日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android PC投屏简单尝试(录屏直播)3—软解章(ImageReader+FFMpeg with X264)

發(fā)布時(shí)間:2025/3/15 Android 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android PC投屏简单尝试(录屏直播)3—软解章(ImageReader+FFMpeg with X264) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

使用FFmpeg進(jìn)行軟件解碼并通過RTMP進(jìn)行推流

  • 編譯帶有x264的FFmpeg
  • 編寫FFmpeg代碼進(jìn)行推流
  • 通過ImageReader的回調(diào),我們就可以得到截屏的數(shù)據(jù)了。第一遍文章是通過自定義的Socket 協(xié)議進(jìn)行傳輸。這里通過FFmpeg,將得到的數(shù)據(jù)進(jìn)行軟件編碼,然后同樣通過RTMP進(jìn)行推流。

    配套使用示意圖.png

    編譯

    去官網(wǎng)下載源碼,并且解壓。按照下面的文件夾路徑進(jìn)行存放。

    ├── ffmpeg├── x264└── others....

    編寫編譯腳本。
    其實(shí)我們是先編譯出libx264.a 然后與ffmpeg進(jìn)行交叉編譯。編譯出完整的libFFmpeg.so 文件。

    腳本放到ffmpeg的目錄下進(jìn)行運(yùn)行就可以了。
    這里需要修改的就是你自己的ndk路徑了

    #!/bin/bash NDK=/Users/Cry/Library/Android/sdk/android-ndk-r14b PLATFORM=$NDK/platforms/android-19/arch-arm/ TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64CPU=arm # PREFIX=$(pwd)/android/$CPU PREFIX=../android-libcd x264function build_one {./configure \--prefix=$PREFIX \--enable-static \--enable-shared \--enable-pic \--disable-asm \--disable-cli \--host=arm-linux \--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \--sysroot=$PLATFORMmake cleanmake -j8make install }build_onecd ..OUT_PREFIX=$(pwd)/android/$CPU # 加入x264編譯庫 EXTRA_CFLAGS="-I./android-lib/include" EXTRA_LDFLAGS="-L./android-lib/lib"function build_two { ./configure \--target-os=linux \--prefix=$OUT_PREFIX \--enable-cross-compile \--enable-runtime-cpudetect \--disable-asm \--disable-doc \--arch=arm \--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \--disable-stripping \--nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \--sysroot=$PLATFORM \--enable-gpl \--enable-static \--disable-shared \--enable-version3 \--enable-small \--enable-libx264 \--enable-encoder=libx264 \--enable-zlib \--disable-ffprobe \--disable-ffplay \--disable-ffmpeg \--disable-ffserver \--extra-cflags=$EXTRA_CFLAGS \--extra-ldflags=$EXTRA_LDFLAGSmake clean make -j8 make install# 這段解釋見后文 $TOOLCHAIN/bin/arm-linux-androideabi-ld -rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -L$OUT_PREFIX/lib -soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o $OUT_PREFIX/libffmpeg.so \android-lib/lib/libx264.a \libavcodec/libavcodec.a \libavfilter/libavfilter.a \libswresample/libswresample.a \libavformat/libavformat.a \libavutil/libavutil.a \libswscale/libswscale.a \libpostproc/libpostproc.a \libavdevice/libavdevice.a \-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker $TOOLCHAIN/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a } build_two

    編譯結(jié)果

    ?

    image.png

    image.png

    ?

    這個就是我們想要的帶有x264的ffmpeg了

    ?

    因?yàn)槲覀冞@里得到的數(shù)據(jù)將是RGBA的數(shù)據(jù),所以我們還需要將其轉(zhuǎn)成YUV420P,進(jìn)行處理。我們需要libyuv,使用這個庫進(jìn)行轉(zhuǎn)換能大大提升我們的效果。而且使用起來非常方便。
    所以我們也將其加入編譯

  • 下載源碼
  • 配置項(xiàng)目
    將源碼全部復(fù)制到

    ?

    image.png

  • 同時(shí)我們注意到,這里面就已經(jīng)配置好Cmake文件了。我只需要將其做一下簡單的修改,就可以使用了

    ?

    image.png

    ?

    將我們不需要的so文件和bin文件的安裝給去掉。

    接下來配置我們自己的cmake文件

    #libyuv include_directories(${CMAKE_SOURCE_DIR}/libs/libyuv/include) # 這樣就可以直接使用內(nèi)部的cmake文件了 add_subdirectory(${CMAKE_SOURCE_DIR}/libs/libyuv) #...部分省略 #同時(shí)將其鏈接到我們自己的庫中,來進(jìn)行使用 target_link_libraries( # Specifies the target library.native-libffmpegyuv# Links the target library to the log library# included in the NDK.${log-lib})

    進(jìn)行代碼的編寫

  • RTMP的鏈接
    同樣,需要先進(jìn)行RTMP的鏈接。FFMpeg不同的是,因?yàn)樽约壕陀芯幋a器,所以可以直接將頭寫到流里。完成publish

  • 使用FFmpeg的必備套路。
    注冊編碼器和網(wǎng)絡(luò)。(因?yàn)檎娴挠杏玫桨?

  • av_register_all();
  • 同樣的套路。在使用編碼器之前,都需要配置編碼器的參數(shù)。
    在FFmpeg中,同樣需要MediaFormat和Encoder。而且ffmpeg 的編程離不開各種上下文對象.所以這里就是先去獲取上下文對象。然后給其配置參數(shù)。進(jìn)行初始化
  • //AVFormat的上下文對象,里面配置format的信息 AVFormatContext *ofmt_ctx;//通過我們給的地址,和''flv'的 格式名稱來分配上下文avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_path);

    這個上下文十分重要和常見。他是包含IO的格式上下文。我們先獲取他。
    接著。我們需要來找到我們的編碼器

    AVCodec *pCodec; //這里直接通過ID進(jìn)行查找 pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!pCodec) {LOGI("Can not find encoder!\n");return -1;}

    找到編碼器之后,同樣,需要先得到編碼器的上下文對象。這個對象也很重要

    pCodecCtx = avcodec_alloc_context3(pCodec); //下面就是對上下文對象的參數(shù)配置//編碼器的ID號,這里為264編碼器,可以根據(jù)video_st里的codecID 參數(shù)賦值pCodecCtx->codec_id = pCodec->id;//像素的格式,也就是說采用什么樣的色彩空間來表明一個像素點(diǎn)pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;//編碼器編碼的數(shù)據(jù)類型pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;//編碼目標(biāo)的視頻幀大小,以像素為單位pCodecCtx->width = width;pCodecCtx->height = height;pCodecCtx->framerate = (AVRational) {fps, 1};//幀率的基本單位,我們用分?jǐn)?shù)來表示,pCodecCtx->time_base = (AVRational) {1, fps};//目標(biāo)的碼率,即采樣的碼率;顯然,采樣碼率越大,視頻大小越大pCodecCtx->bit_rate = 400000;//固定允許的碼率誤差,數(shù)值越大,視頻越小 // pCodecCtx->bit_rate_tolerance = 4000000;pCodecCtx->gop_size = 50;/* Some formats want stream headers to be separate. */if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;

    這里主要配置的都是一些常見的參數(shù)。包括編碼器的ID,視頻的長寬信息,比特率,幀率,時(shí)基和gop_size

    接著配置一些 H.264需要的參數(shù)

    //H264 codec param // pCodecCtx->me_range = 16;//pCodecCtx->max_qdiff = 4;pCodecCtx->qcompress = 0.6;//最大和最小量化系數(shù)pCodecCtx->qmin = 10;pCodecCtx->qmax = 51;//Optional Param//兩個非B幀之間允許出現(xiàn)多少個B幀數(shù)//設(shè)置0表示不使用B幀//b 幀越多,圖片越小pCodecCtx->max_b_frames = 0;// Set H264 preset and tuneAVDictionary *param = 0;//H.264if (pCodecCtx->codec_id == AV_CODEC_ID_H264) { // av_dict_set(&param, "preset", "slow", 0);/*** 這個非常重要,如果不設(shè)置延時(shí)非常的大* ultrafast,superfast, veryfast, faster, fast, medium* slow, slower, veryslow, placebo. 這是x264編碼速度的選項(xiàng)*/av_dict_set(&param, "preset", "superfast", 0);av_dict_set(&param, "tune", "zerolatency", 0);}

    這里有兩個必須要注意的地方。

  • pCodecCtx->qcompress = 0.6;
    //最大和最小量化系數(shù)
    pCodecCtx->qmin = 10;
    pCodecCtx->qmax = 51;
    這幾個參數(shù)必須配置對。如果不是這樣的話,好像是會出錯的。

  • 編碼速度的選項(xiàng)。這個也很有影響

  • 接著配置完參數(shù),我們就開啟encoder

    if (avcodec_open2(pCodecCtx, pCodec, &param) < 0) {LOGE("Failed to open encoder!\n");return -1;}

    因?yàn)槲覀冞@兒只推流視頻,所以,我們還需要創(chuàng)建一個stream.將我們的編碼器信息同樣保存到這個視頻流中

    //Add a new stream to output,should be called by the user before avformat_write_header() for muxingvideo_st = avformat_new_stream(ofmt_ctx, pCodec);if (video_st == NULL) {return -1;}video_st->time_base.num = 1;video_st->time_base.den = fps; // video_st->codec = pCodecCtx;video_st->codecpar->codec_tag = 0;avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);

    最后,就是通過avio_open 打開鏈接,進(jìn)行鏈接。
    并且我們知道進(jìn)行推流,必須先將其頭部的編碼器信息寫入,才可以。所以同樣
    avformat_write_header 寫入信息,這樣,publish RTMP成功了。

    //Open output URL,set before avformat_write_header() for muxingif (avio_open(&ofmt_ctx->pb, out_path, AVIO_FLAG_READ_WRITE) < 0) {LOGE("Failed to open output file!\n");return -1;}//Write File Headeravformat_write_header(ofmt_ctx, NULL);return 0;

    接下來,就是推送實(shí)際的nal了

  • RTMP數(shù)據(jù)的發(fā)送
    回顧ImageReader的配置
  • imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 5);

    我們要求輸出的是RGBA格式的Image數(shù)據(jù)。

    通過ImageReader的回調(diào),我們可以得到Image數(shù)據(jù)

    @Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();if (image != null) {long timestamp = image.getTimestamp();if (this.timestamp == 0) {this.timestamp = timestamp;if (VERBOSE) {Log.d(TAG, "onImageAvailable timeStamp=" + this.timestamp);}} else {if (VERBOSE) {long delta = timestamp - this.timestamp;Log.d(TAG, "onImageAvailable timeStamp delta in ms=" + delta / 1000000);}}Image.Plane[] planes = image.getPlanes();//因?yàn)槲覀円蟮氖荝GBA格式的數(shù)據(jù),所以全部的存儲在planes[0]中Image.Plane plane = planes[0];//由于Image中的緩沖區(qū)存在數(shù)據(jù)對齊,所以其大小不一定是我們生成ImageReader實(shí)例時(shí)指定的大小,//ImageReader會自動為畫面每一行最右側(cè)添加一個padding,以進(jìn)行對齊,對齊多少字節(jié)可能因硬件而異,//所以我們在取出數(shù)據(jù)時(shí)需要忽略這一部分?jǐn)?shù)據(jù)。int rowStride = plane.getRowStride();int pixelStride = plane.getPixelStride();int rowPadding = rowStride - pixelStride * width;ByteBuffer buffer = plane.getBuffer();//將得到的buffer 和 寬高傳入進(jìn)行處理FFmpegSender.getInstance().rtmpSend(buffer, height, width * 4, rowPadding);image.close();}}

    發(fā)送的方法。
    1.我們這里傳入了未編碼的RGBA數(shù)據(jù),需要先轉(zhuǎn)成YUV420P.
    AVFrame來保存未編碼的數(shù)據(jù)。所以我們需要先給其分配內(nèi)存空間和數(shù)據(jù)

    pFrameYUV = av_frame_alloc();int picture_size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width,pCodecCtx->height, 1);uint8_t *buffers = (uint8_t *) av_malloc(picture_size);//將buffers的地址賦給AVFrame中的圖像數(shù)據(jù),根據(jù)像素格式判斷有幾個數(shù)據(jù)指針av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffers, pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height, 1);

    然后,我們將我們的數(shù)據(jù)轉(zhuǎn)成yuv,并且將數(shù)據(jù)傳遞給pFrameYUV

    //之前我們說過,得到的數(shù)據(jù)是有字節(jié)對齊的問題的。我們在這里,進(jìn)行處理。得到真正的argb數(shù)據(jù)jbyte *srcBuffer = static_cast<jbyte *>(env->GetDirectBufferAddress(buffer));jbyte *dest = new jbyte[yuv_width * yuv_height * 4];int offset = 0;for (int i = 0; i < row;i++) {memcpy(dest + offset, srcBuffer + offset + i * rowPadding, stride);offset += stride;}

    利用 libyuv 將數(shù)據(jù)轉(zhuǎn)成yuv420p,同時(shí)保存起來

    libyuv::ConvertToI420((uint8_t *) dest, yuv_width * yuv_height,pFrameYUV->data[0], yuv_width,pFrameYUV->data[1], yuv_width / 2,pFrameYUV->data[2], yuv_width / 2,0, 0,yuv_width, yuv_height,yuv_width, yuv_height,libyuv::kRotate0, libyuv::FOURCC_ABGR);
  • 我們需要將數(shù)據(jù)送入編碼器進(jìn)行編碼
    先配置參數(shù)
  • pFrameYUV->pts = count;pFrameYUV->format = AV_PIX_FMT_YUV420P;pFrameYUV->width = yuv_width;pFrameYUV->height = yuv_height;

    AVPacket是存儲編碼之后的數(shù)據(jù)的。我們需要進(jìn)行的操作就是將AVFrame送入編碼器,然后得到AVPacket. 所以我們對其進(jìn)行初始化。并且按照上面所說。來得到包含編碼數(shù)據(jù)的AvPacket

    //例如對于H.264來說。1個AVPacket的data通常對應(yīng)一個NAL//初始化AVPacketav_init_packet(&enc_pkt);//開始編碼YUV數(shù)據(jù)ret = avcodec_send_frame(pCodecCtx, pFrameYUV);if (ret != 0) {LOGE("avcodec_send_frame error");return -1;}//獲取編碼后的數(shù)據(jù)ret = avcodec_receive_packet(pCodecCtx, &enc_pkt);//是否編碼前的YUV數(shù)據(jù)av_frame_free(&pFrameYUV);if (ret != 0 || enc_pkt.size <= 0) {LOGE("avcodec_receive_packet error");avError(ret);return -2;}

    得到編碼后的數(shù)據(jù),再對其進(jìn)行參數(shù)配置,需要注意的pts 和dts的配置,這里的方式不對。這里把他當(dāng)作是恒定的幀率來處理來。但實(shí)際上,因?yàn)橛僧?dāng)前的實(shí)際來決定。

    enc_pkt.stream_index = video_st->index;AVRational time_base = ofmt_ctx->streams[0]->time_base;//{ 1, 1000 };enc_pkt.pts = count * (video_st->time_base.den) / ((video_st->time_base.num) * fps);enc_pkt.dts = enc_pkt.pts;enc_pkt.duration = (video_st->time_base.den) / ((video_st->time_base.num) * fps);LOGI("index:%d,pts:%lld,dts:%lld,duration:%lld,time_base:%d,%d",count,(long long) enc_pkt.pts,(long long) enc_pkt.dts,(long long) enc_pkt.duration,time_base.num, time_base.den);enc_pkt.pos = -1;
  • 我們需要通過RTMP協(xié)議進(jìn)行發(fā)送數(shù)據(jù)
    這部分很簡單,只要調(diào)用write方法就可以完成了。
  • ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);if (ret != 0) {avError(ret);LOGE("av_interleaved_write_frame failed");}count++;return 0;

    最后是關(guān)閉的方法。
    關(guān)閉的時(shí)候,我們需要釋放掉我們創(chuàng)建的IO鏈接/AVFormatContext和Encoder。

    if (video_st)//encode包含在流中了avcodec_close(video_st->codec);if (ofmt_ctx) {//網(wǎng)絡(luò)的指針保留在AVFormatContext中avio_close(ofmt_ctx->pb);//同時(shí)自己也要釋放avformat_free_context(ofmt_ctx);ofmt_ctx = NULL;}return 0;

    總結(jié)

    需要注意的兩點(diǎn)

    1. FFmpeg的裁剪編譯

    直接編譯出來的so文件巨大。在APK文件中6M大小。

    • 定位裁剪需求
      我們根據(jù)之前的文章,來分析和定位裁剪的腳本。
      整個流程中,我們只需要libx264 的編碼器。flv的muxer 和 RTMP協(xié)議。因?yàn)镽TMP協(xié)議是基于TCP的。所以我們也打開tcp協(xié)議。

    • 編寫腳本
      基于上面的分析,我們修改了FFmpeg的配置

    #!/bin/bash NDK=/Users/Cry/Library/Android/sdk/android-ndk-r14b PLATFORM=$NDK/platforms/android-19/arch-arm/ TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64CPU=arm # PREFIX=$(pwd)/android/$CPU PREFIX=../android-libcd x264function build_one {./configure \--prefix=$PREFIX \--enable-static \--enable-shared \--enable-pic \--disable-asm \--disable-cli \--host=arm-linux \--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \--sysroot=$PLATFORM \--extra-cflags="-fPIC -marm -DX264_VERSION -DANDROID -DHAVE_PTHREAD -DNDEBUG -static -D__ARM_ARCH_7__ -D__ARM_ARCH_7A__ -O3 -march=armv7-a -mfpu=neon -mtune=generic-armv7-a -mfloat-abi=softfp -ftree-vectorize -mvectorize-with-neon-quad -ffast-math" \make cleanmake -j8make install }build_onecd ..OUT_PREFIX=$(pwd)/android/$CPU # 加入x264編譯庫 EXTRA_CFLAGS="-I./android-lib/include" EXTRA_LDFLAGS="-L./android-lib/lib"function build_two { ./configure \--target-os=linux \--prefix=$OUT_PREFIX \--enable-cross-compile \--enable-runtime-cpudetect \--disable-asm \--disable-doc \--arch=arm \--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \--disable-stripping \--nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \--sysroot=$PLATFORM \--enable-gpl \--enable-static \--disable-shared \--enable-version3 \--enable-small \--enable-libx264 \--disable-encoders \--enable-encoder=libx264 \--disable-muxers \--enable-muxer=flv \--enable-muxer=h264 \--disable-decoders \--disable-demuxers \--disable-parsers \--enable-parser=aac \--enable-parser=h264 \--disable-protocols \--enable-protocol=file \--enable-protocol=ffrtmphttp \--enable-protocol=rtmp \--enable-protocol=tcp \--disable-filters \--disable-bsfs \--disable-indevs \--disable-outdevs \--disable-ffprobe \--disable-ffplay \--disable-ffmpeg \--disable-ffserver \--extra-cflags=$EXTRA_CFLAGS \--extra-ldflags=$EXTRA_LDFLAGSmake clean make -j8 make install$TOOLCHAIN/bin/arm-linux-androideabi-ld -rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -L$OUT_PREFIX/lib -soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o $OUT_PREFIX/libffmpeg.so \android-lib/lib/libx264.a \libavcodec/libavcodec.a \libavfilter/libavfilter.a \libswresample/libswresample.a \libavformat/libavformat.a \libavutil/libavutil.a \libswscale/libswscale.a \libpostproc/libpostproc.a \libavdevice/libavdevice.a \-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker $TOOLCHAIN/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a } build_two

    image.png

    • 結(jié)果
      原大小

      ?

      image.png

    現(xiàn)在的大小

    ?

    image.png

    在APK中的大小

    ?

    image.png

    完美~~

    ?



    作者:deep_sadness
    鏈接:https://www.jianshu.com/p/6559567a973c
    來源:簡書
    簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。

    總結(jié)

    以上是生活随笔為你收集整理的Android PC投屏简单尝试(录屏直播)3—软解章(ImageReader+FFMpeg with X264)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。