OpenGL.Shader:志哥教你写一个滤镜直播客户端(6)NdkMediaCodec编码输出h264/h265

OpenGL.Shader:志哥教你写一个滤镜直播客户端(6)NdkMediaCodec编码输出h264/h265

最近复工所以很忙,距离上一篇文章拖了有一段时间。在对应的编码上确实遇上了些小问题所以导致了时间比较长。 废话不说了,本篇内容主要内容是,利用Ndk版本的MediaCodec,编码之前经过各种滤镜处理后的摄像头视频流。项目路径:https://github.com/MrZhaozhirong/NativeCppApp

有读者在阅读我之前写的《OpenGL.ES在Android上的简单实践—水印录制》系列文章的时候咨询过,如何把水印后的视频编码输出h264/h265流,方便进行网络传输?他们直觉的传统思维是把水印(也可以说是增加滤镜)后的数据流从内存读取出来,然后再利用各种编码器进行编码输出。这是一种不错的做法,但也是一种低效的做法。

高效的办法就是利用系统的MediaCodec编码功能当中的一个方法,其中在Framework的Java层有一个createInputSurface方法,我们来看看其功能描述。重点内容已经翻译成中文了,总结概括就是:(1)用于编码输入,所见即所得,所绘即所编(2)必须使用硬件加速的API(OpenGL.ES / Vulkan)去渲染(3)谁使用谁回收。

/**
 * Requests a Surface to use as the input to an encoder, in place of input buffers.  
 * This may only be called after {@link #configure} and before {@link #start}.
 * 申请求一个Surface用作编码器的输入,代替其缓冲区的输入。
 * The application is responsible for calling release() on the Surface when done.
 * 完成后,应用程序负责在Surface上调用release()。
 * The Surface must be rendered with a hardware-accelerated API, such as OpenGL ES.
 * 此Surface必须使用硬件加速的API(如OpenGL ES)去渲染。
 * {@link android.view.Surface#lockCanvas(android.graphics.Rect)} may fail or produce
 * unexpected results.
 * @throws IllegalStateException if not in the Configured state.
 */
@NonNull
public native final Surface createInputSurface(); // 这里注意!此方法是直接通过native层实现的方法。

这么一看soeasy啊。那么究竟代码如何编写呢?在ndk层如何使用这个inputSurface进行编码呢?先回想一下用于输出显示的Surface是如何draw的。按照其流程在这个InputSurface再draw一遍,就完成其编码输入啦。事不宜迟,show code。

定义CodecEncoder类,用于视频编码,其.h头文件如下结构所示:

#ifndef CODEC_ENCODER_H
#define CODEC_ENCODER_H

#define MIME_TYPE_NONE 0x1000
#define MIME_TYPE_H264 0x1201
#define MIME_TYPE_H265 0x1202
#define NDK_MEDIACODEC_BUFFER_FLAG_KEY_FRAME 1

class CodecEncoder {
public:
    CodecEncoder();
    ~CodecEncoder();
    // Encoder core
    void            setMetaConfig(int mimeType, int width, int height,
                                  int iFrameInterval, int frameRate, int bitRate);
    bool            initEglWindow();
    void            releaseEglWindow();
    bool            initMediaCodec();
    void            releaseMediaCodec();
    void            startEncode();
    void            stopEncode();
    // Encoder logic
    void            encoderCreated();
    void            encoderChanged(int width, int height);
    void            encoderOnDraw(GLuint mYSamplerId, GLuint mUSamplerId, GLuint mVSamplerId,
                                 float* positionCords, float* textureCords);
    void            renderDestroyed() ;
    void            setFilter(int filter_type_id);
    void            adjustFilterValue(int value, int max);
private:
    EglCore*        mEglCore;
    WindowSurface*  mWindowSurface;
    GpuBaseFilter*  mFilter;
    int             mRequestTypeId;
    int             mCurrentTypeId;
    float           mFilterEffectPercent;

private:
    pthread_t       encoder_thread_t;
    bool            requestStopEncoder;
    pthread_mutex_t mutex;
    //encoder thread
    static void*    onEncoderThreadStub(void *p)
    {
        (static_cast<CodecEncoder*>(p))->onEncoderThreadProc();
        return NULL;
    }
    void            onEncoderThreadProc();


    int             mWidth;
    int             mHeight;
    int             mimeType; // 默认H264
    int             iFrameInterval; // I帧间隔(单位秒)
    int             frameRate; // 帧率
    int             bitRate; // 码率 bit per second
    AMediaCodec*    mCodecRef;
    ANativeWindow*  mWindowRef;
    bool            isPrepareWindow = false;
    bool            isPrepareCodec = false;

    uint8_t*        pps_sps_header;
    int             header_size;
    void            saveConfigPPSandSPS(uint8_t* data, int32_t data_size);
private:
    DISALLOW_EVIL_CONSTRUCTORS(CodecEncoder);
    bool            isDebug = true;
    void            debugWriteOutputFile(uint8_t* data, int32_t data_size, bool bNeedPack_PPS_SPS);
};
#endif // CODEC_ENCODER_H

内容组成如上,都带上了便于理解的注释。接下来介绍使用的逻辑流程,看看方法具体的实现。

1、在GpuFilterRender上添加一个CodecEncoder实例 和 定时器CELLTimer,用于计算输入的NV21的帧率。

class GpuFilterRender : public GLRender,
                        public CELL::CELLTimerHandler
{
    // ...
private:
    // 编码器实例
    CodecEncoder    mEncoder;
    // 定时器,动态计算输入的NV21帧率
    CELL::CELLTimer mFpsTimer;
    int             mCurrentInputFps;
    int static      mStaticInputFps;
    // CELLTimer定时器Callback,动态计算输入的NV21帧率
    virtual void handlerCallback()
    {
        mStaticInputFps = mCurrentInputFps;
        LOGI("mCurrentInputFps : %d",mCurrentInputFps);
        mCurrentInputFps = 0;
    }
}

2、在GpuFilterRender的三大生命周期回调函数上追加CodecEncoder的生命函数。内容如下

void GpuFilterRender::surfaceCreated(ANativeWindow *window)
{
    // ... 注意区别于显示的EGL.Surface
    mWindowSurface->swapBuffers();

    int32_t windowWidth = ANativeWindow_getWidth(window);
    int32_t windowHeight = ANativeWindow_getHeight(window);
    // 和surfaceChanged传入的width和height是一样的,但是这里就可以提前激活AMediaCodec
    // 推荐使用bitRate = Width*Height*FrameRate * Factor的公式结合产品的使用场景进行设置。
    int mDesiredFps = 20;
    int iFrameInterval = 2;
    int bitRate = static_cast<int>(windowWidth * windowHeight * mDesiredFps * 0.22f);
    mEncoder.setMetaConfig(MIME_TYPE_H264, windowWidth, windowHeight, iFrameInterval, mDesiredFps, bitRate);
    mEncoder.encoderCreated();

    mFpsTimer.setTimer(1,0,this); //this实现回调函数,即handlerCallback
    mFpsTimer.startTimer();
}
void GpuFilterRender::surfaceChanged(int width, int height)
{
    this->mViewWidth = width;
    this->mViewHeight = height;
    mWindowSurface->makeCurrent();
    mFilter->onOutputSizeChanged(width, height);
    mWindowSurface->swapBuffers();
    mEncoder.encoderChanged(width, height);
    mEncoder.startEncode();
}
void GpuFilterRender::surfaceDestroyed()
{
    // ...
    mEncoder.renderDestroyed();
    mEncoder.stopEncode();
    mFpsTimer.stopTimer();
}

接着来看CodecEncoder的函数实现,

void CodecEncoder::setMetaConfig(int mimeType, int width, int height,
                                 int iFrameInterval, int frameRate, int bitRate) {
    this->mimeType = mimeType;
    this->mWidth = width;
    this->mHeight = height;
    this->iFrameInterval = iFrameInterval;
    this->frameRate = frameRate;
    this->bitRate = bitRate;
}
void CodecEncoder::encoderCreated() {
    if(mWidth==0||mHeight==0){
        LOGW("MetaData wrong! width*height %dx%d",mWidth,mHeight);
        return;
    }
    initMediaCodec();
    initEglWindow();
    mWindowSurface->makeCurrent();
    if( mFilter==NULL) {
        mFilter = new GpuBaseFilter();
    } else {
        mFilter->destroy();
    }
    mFilter->init();
    mRequestTypeId = mCurrentTypeId = mFilter->getTypeId();
    mWindowSurface->swapBuffers();
}

引出重点函数 initMediaCodec,实现如下: 

bool CodecEncoder::initMediaCodec() {
    if( isPrepareCodec ){
        return isPrepareCodec;
    }
    // Java.MediaFormat.MIMETYPE_VIDEO_AVC = "video/avc";
    // Java.MediaFormat.MIMETYPE_VIDEO_HEVC = "video/hevc";
    std::string mime;
    if(mimeType == MIME_TYPE_H264) {
        mime = "video/avc";
    }
    if(mimeType == MIME_TYPE_H265) {
        mime = "video/hevc";
    }
    mCodecRef = AMediaCodec_createEncoderByType(mime.c_str());

    AMediaFormat* format = AMediaFormat_new();
    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME,   mime.c_str());
    AMediaFormat_setInt32( format, AMEDIAFORMAT_KEY_WIDTH,  mWidth);
    AMediaFormat_setInt32( format, AMEDIAFORMAT_KEY_HEIGHT, mHeight);
    AMediaFormat_setInt32( format, AMEDIAFORMAT_KEY_BIT_RATE, bitRate);
    AMediaFormat_setInt32( format, AMEDIAFORMAT_KEY_FRAME_RATE, frameRate);
    AMediaFormat_setInt32( format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, iFrameInterval);
    // public static final int COLOR_FormatSurface = 0x7F000789;
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, 0x7F000789);
#if __ANDROID_API__ >= 28
    // //Constant quality mode 忽略用户设置的码率,由编码器自己控制码率,并尽可能保证画面清晰度和码率的均衡
    // public static final int BITRATE_MODE_CQ = 0;
    // //Variable bitrate mode
    // //尽可能遵守用户设置的码率,但是会根据帧画面之间运动矢量(通俗理解就是帧与帧之间的画面变化程度)
    // //来动态调整码率,如果运动矢量较大,则在该时间段将码率调高,如果画面变换很小,则码率降低
    // public static final int BITRATE_MODE_VBR = 1;
    // //Constant bitrate mode 无论视频的画面内容如果,尽可能遵守用户设置的码率
    // public static final int BITRATE_MODE_CBR = 2;
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BITRATE_MODE, 0); //默认1-VBR
    //以上参考链接 https://segmentfault.com/a/1190000021223837?utm_source=tag-newest
#endif
    LOGI("AMediaCodec_configure format : %s", AMediaFormat_toString(format));
    media_status_t rc = AMediaCodec_configure(mCodecRef, format, NULL, NULL, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
    AMediaFormat_delete(format);
    LOGI("CodecEncoder AMediaCodec_configure %d ", rc);
#if __ANDROID_API__ >= 26
    // after {@link #configure} and before {@link #start}.
    media_status_t ret = AMediaCodec_createInputSurface(mCodecRef, &mWindowRef);
#else
    // 参照源码,自编写AMediaCodec_createInputSurface
    media_status_t ret = MY_AMediaCodec_createInputSurface(mCodecRef, &mWindowRef);
#endif
    if(AMEDIA_OK == ret) {
        ret = AMediaCodec_start(mCodecRef);
        LOGI("CodecEncoder AMediaCodec_start %d ",ret);
        isPrepareCodec = true;
        return (ret==AMEDIA_OK);
    } else {
        LOGW("CodecEncoder AMediaCodec_createInputSurface != OK. ");
        AMediaCodec_delete(mCodecRef);
        isPrepareCodec = false;
        return false;
    }
}

这个方法中的内容涉及很多知识点,还有一个难点,这里一一道来。

扫描二维码关注公众号,回复: 12021791 查看本文章

首先是各种AMEDIAFORMAT的配置参数,如何设置?(https://segmentfault.com/a/1190000021223837?utm_source=tag-newest)这里找到一篇大佬的理论文章,我自己也在实际运用当中参照这篇文章的经验,这里借来参考参考。其中说到

结合以上的分析,为了当我们在使用MediaCodec进行视频编码时,为了达到编码速度和画面清晰度的平衡,我们需要在通过几个方面进行综合的优化。

1. Profile方法
综合上文的介绍,为了保证app在各个Android机型上的完美适配,其实我们在Profile方面能做的选择不多,最安全的情况是将Profile设置为Baseline。

2. Bitrate方法
Bitrate的设置我推荐使用Biterate = Width Height FrameRate * Factor的公式结合产品的使用场景进行设置。

3. Biterate Mode方法
Biterate Mode的默认设置是BITRATE_MODE_VBR,我推荐在系统和机型支持的情况下尽量将Biterate Mode设置为BITRATE_MODE_CQ。
在BITRATE_MODE_CQ情况下,编码器自身对码率和编码速度的调节往往能达到理想的效果,生成的视频文件不至于过多,但是画面清晰度优秀。
当然也要注意做好系统和机型的适配,进行异常处理,因为在某些机型上可能出现不支持BITRATE_MODE_CQ而导致MediaCodec在configure方法时失败,那么此时,我们需要回退到系统默认的Biterate Mode了。

参照其上经验:

第一个Profile不需要额外设置,默认就是Baseline了;

第二个Bitrate设置为 windowWidth * windowHeight * mDesiredFps * 0.22f;

第三个Biterate Mode就有点考究了,在Java层当中,MediaCodec 流控相关的接口并不多,一是配置时设置目标码率和码率控制模式,二是动态调整目标码率(Android 19+)。其代码是 MediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR); 在Ndk层发现只有在__ANDROID_API__ >= 28的版本上才有AMEDIAFORMAT_KEY_BITRATE_MODE的字段,所以这里建议在API不支持的情况下不进行设置,使用默认选项。

接着往下走:

需要设置AMEDIAFORMAT_KEY_COLOR_FORMAT,这里在Java层选择的项应该是MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface,对应的真实数值为0x7F000789,我们直接复制粘贴使用之。

AMediaCodec_createInputSurface  __INTRODUCED_IN(26)???

设置完AMediaFormat之后,就可以configure->createInputSurface->start启动编码器,大难题才刚刚到来了。AMediaCodec_createInputSurface __INTRODUCED_IN(26); 怎么这个方法只有在26以上才支持呢?Google这是闹着哪里玩呢?Java层的MediaCodec在4.4推出5.0完善,Ndk层的AMediaCodec也在5.0就退出并完善?怎么着这方法要__ANDROID_API__ >= 26 ?那怎么办?不给我用那我就自己弄一个呗。利用之前介绍的http://androidxref.com/ (使用教程
找到AMediaCodec_createInputSurface的源码
(androidxref.com/8.1.0_r33/xref/frameworks/av/media/ndk/NdkMediaCodec.cpp#395) 

以及 Java层的MediaCodec.createInputSurface的源码
(http://androidxref.com/8.1.0_r33/xref/frameworks/base/media/java/android/media/MediaCodec.java#createInputSurface 跳转
http://androidxref.com/8.1.0_r33/xref/frameworks/base/media/jni/android_media_MediaCodec.cpp#android_media_MediaCodec_createInputSurface 再转
http://androidxref.com/8.1.0_r33/xref/frameworks/av/media/libstagefright/MediaCodec.cpp#865)

参照源码实现:

media_status_t CodecEncoder::MY_AMediaCodec_createInputSurface(AMediaCodec *mData, ANativeWindow **surface) {
    if (surface == NULL || mData == NULL) {
        return AMEDIA_ERROR_INVALID_PARAMETER;
    }
    *surface = NULL;
    sp<IGraphicBufferProducer> igbp = NULL;
    status_t err = mData->mCodec->createInputSurface(&igbp);
    *surface = new Surface(igbp);
    ANativeWindow_acquire(*surface);
    return AMEDIA_OK;
}

实现不难,但怎么编译运行?IGraphicBufferProducer和Surface哪里来?这就需要我们自己下载源码(下载教程1 / 下载教程2

下载完之后,需要在CMakeList脚本上增加相关源码的路径,并加入相应的链接库:

aux_source_directory(./src/main/cpp/gpufilter GPU_FILTER_SRC )
aux_source_directory(./src/main/cpp/gpufilter/components GPU_FILTER_COM_SRC )
aux_source_directory(./src/main/cpp/gpufilter/render GPU_FILTER_RENDER_SRC )
aux_source_directory(./src/main/cpp/gpufilter/codec GPU_FILTER_CODEC_SRC )

include_directories(G:/android-sys-src/android-8.1.0_r8/av/media/libstagefright/include)
include_directories(G:/android-sys-src/android-8.1.0_r8/native/libs/gui/include)
include_directories(G:/android-sys-src/android-8.1.0_r8/native/libs/ui/include)

add_library(
        gpu-filter
        SHARED
        ${COMMON_SRC}
        ${EGL_SRC}
        ${PROGRAM_SRC}
        ${GPU_FILTER_SRC}
        ${GPU_FILTER_COM_SRC}
        ${GPU_FILTER_RENDER_SRC}
        ${GPU_FILTER_CODEC_SRC}
        )
target_link_libraries(
        gpu-filter
        EGL GLESv2 GLESv3
        #GUI UI UTILS
        android mediandk OpenMAXAL
        log)

# mediandk参数 for native codec
# OpenMAXAL参数 for 开放多媒体应用加速层
# android参数 for native windows

到此才算真正完成initMediaCodec,然后转到 initEglWindow,就比较简单了:

bool CodecEncoder::initEglWindow() {
    if( isPrepareWindow ){
        return isPrepareWindow;
    }
    if (mWindowRef == NULL) {
        LOGW("SurfaceWindow is null, Call initMediaCodec before initEglWindow. ");
        return false;
    }
    if (mEglCore == NULL)
        mEglCore = new EglCore(eglGetCurrentContext(), FLAG_TRY_GLES2|FLAG_RECORDABLE);
    if (mWindowSurface == NULL)
        mWindowSurface = new WindowSurface(mEglCore, mWindowRef, true);

    assert(mWindowSurface != NULL && mEglCore != NULL);
    isPrepareWindow = true;
    return true;
}

 到此,CodecEncoder.encoderCreated()初始化分析完毕。

一样的代码,不一样的EGL。

分析了CodecEncoder::encoderCreated->initMediaCodec/initEglWindow,接下来是CodecEncoder::encoderChanged->startEncode。AMediaCodec编码的工作线程

void CodecEncoder::startEncode() {
    if( encoder_thread_t == 0) {
        requestStopEncoder = false;
        pthread_create(&encoder_thread_t, NULL, onEncoderThreadStub, this);
    }
}
//encoder thread sub
static void*    onEncoderThreadStub(void *p)
{
    (static_cast<CodecEncoder*>(p))->onEncoderThreadProc();
    return NULL;
}
//encoder thread impl
void CodecEncoder::onEncoderThreadProc() {
    int TIMEOUT_USEC = 2000;
    bool finish = false;
    bool doRender = false;

    while ( !finish ){
        if( mCodecRef==NULL) {
            finish = true;
            continue;
        }
        try {
            if( requestStopEncoder ){
                media_status_t rc = AMediaCodec_signalEndOfInputStream(mCodecRef);
                if(isDebug) LOGI("Stop requested, send BUFFER_FLAG_END_OF_STREAM to AMediaCodecEncoder %d.", rc);
                //int buff_idx = AMediaCodec_dequeueInputBuffer(mCodecRef, TIMEOUT_USEC);
                //### Error:dequeueInputBuffer can't be used with input surface
                //if (buff_idx >= 0) {
                //    AMediaCodec_queueInputBuffer(mCodecRef,buff_idx, 0, 0, 0L, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
                //}
            }
            // 从编码output队列 获取解码结果 清空解码状态
            AMediaCodecBufferInfo info;
            auto status = AMediaCodec_dequeueOutputBuffer(mCodecRef, &info, TIMEOUT_USEC);

            if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
                if (isDebug) LOGV("no output available yet");
            } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
                if (isDebug) LOGI("encoder output buffers changed");
            } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
                auto format = AMediaCodec_getOutputFormat(mCodecRef);
                if (isDebug) LOGI("format changed to: %s", AMediaFormat_toString(format));
                AMediaFormat_delete(format);
            } else if (status < 0) {
                if (isDebug) LOGE("unexpected result from encoder.dequeueOutputBuffer: " + status);
            } else {
                if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
                    if (isDebug) LOGI("output EOS");
                    finish = true;
                }
                size_t BufferSize;
                uint8_t* outputBuf = AMediaCodec_getOutputBuffer(mCodecRef, status, &BufferSize);
                if (outputBuf == nullptr) {
                    if (isDebug) LOGW("FBI WARMING: outputBuf nullptr!");
                    continue;
                }

                if (info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) {
                    // 标记为BUFFER_FLAG_CODEC_CONFIG的缓冲区包含编码数据(PPS SPS)
                    if (isDebug) LOGI("capture Video BUFFER_FLAG_CODEC_CONFIG.");
                    saveConfigPPSandSPS(outputBuf, info.size);
                    debugWriteOutputFile(outputBuf, info.size, false);
                } else if (info.flags & AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) {
                    if (isDebug) LOGI("capture Video BUFFER_FLAG_KEY_FRAME.");
                    debugWriteOutputFile(outputBuf, info.size, false);
                } else {
                    //if (isDebug) LOGI("capture Frame AMediaCodecBufferInfo.flags %d.", info.flags);
                    debugWriteOutputFile(outputBuf, info.size, false);
                }
                int64_t pts = info.presentationTimeUs;
                //if (isDebug) LOGD("AMediaCodec_getOutputBuffer 容器空间BufferSize : %d", BufferSize);
                if (isDebug) LOGD("AMediaCodec_getOutputBuffer 有效数据InfoSize : %d, PTS : %lld", info.size, pts);
                AMediaCodec_releaseOutputBuffer(mCodecRef, status, doRender);
            }
        }catch(char *str) {
            LOGE("AMediaCodec_encode_thread_err : %s", str);
            finish = true;
        }
    }
    releaseMediaCodec();
    releaseEglWindow();
    if(isDebug) LOGW("Encoder thread exiting");
}

还有两个辅助函数saveConfigPPSandSPS / debugWriteOutputFile,看命名得知其工作内容。

void CodecEncoder::saveConfigPPSandSPS(uint8_t* data, int32_t data_size){
    if(pps_sps_header==NULL) {
        pps_sps_header = (uint8_t *) malloc(sizeof(uint8_t)*data_size);
    } else {
        int pps_sps_header_length = sizeof(pps_sps_header)/sizeof(pps_sps_header[0]);
        if(pps_sps_header_length < data_size)
            pps_sps_header = (uint8_t *) realloc(pps_sps_header, sizeof(int8_t)*data_size);
    }
    header_size = data_size;
    memset(pps_sps_header, 0, sizeof(uint8_t)*data_size);
    memcpy(pps_sps_header, data, sizeof(uint8_t)*data_size);
}
void CodecEncoder::debugWriteOutputFile(uint8_t* data, int32_t data_size, bool bNeedPack_PPS_SPS){
    FILE *fp=fopen("/storage/emulated/0/Android/data/org.zzrblog.nativecpp/files/debug.h264","a+");
    /* 自己创建目录 确保目录有效性*/
    if(fp){
        if(bNeedPack_PPS_SPS && pps_sps_header!=NULL) {
            size_t re = fwrite(pps_sps_header, sizeof(uint8_t), header_size, fp);
        }
        fwrite(data, sizeof(uint8_t), data_size, fp);
        fflush(fp);
        fclose(fp);
    }
}

以上都是MediaCodec编码器工作的模板代码,(1)当AMediaCodecBufferInfo.flags == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG表示输出的是配置信息SPS和PPS的数据流,有些情况需要用于包装,暂且记录到内存当中;(2)AMEDIACODEC_BUFFER_FLAG_KEY_FRAME = 1为自定义的宏定义,原来的flag被系统优化掉了;(3)工作线程一直循环运行着,直到遇上终结符AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM 或者是在请求申请退出requestStopEncoder之后插入终结符;

剩下的就是视频流的绘制渲染函数。我们是在GpuFilterRender.renderOnDraw回调当中处理视频视频流并显示到Surface上,同理定义encoderOnDraw,用于把视频流绘制输入到编码inputSurface中。仔细看代码你会发现,其实两个方法的代码逻辑是一样的,区别在于它们运行是不一样的EGL环境。

// 注意对象,这是GpuFilterRender
void GpuFilterRender::renderOnDraw(double elpasedInMilliSec)
{
    //...
    // 画面渲染
    mWindowSurface->makeCurrent();
    yTextureId = updateTexture(dst_y, yTextureId, mFrameWidth, mFrameHeight);
    uTextureId = updateTexture(dst_u, uTextureId, mFrameWidth/2, mFrameHeight/2);
    vTextureId = updateTexture(dst_v, vTextureId, mFrameWidth/2, mFrameHeight/2);
    checkFilterChange();
    if( mFilter!=NULL) {
        mFilter->setAdjustEffect(mFilterEffectPercent);
        mFilter->onDraw(yTextureId, uTextureId, vTextureId, positionCords, textureCords);
    }
    // 1s=1,000ms=1,000,000us=1,000,000,000ns
    static int64_t count = 0;
    if(mStaticInputFps!=0) {
        count++;
        long frame_interval = 1000000000L / mStaticInputFps;
        mWindowSurface->setPresentationTime(frame_interval);
    }
    mWindowSurface->swapBuffers();
    // 视频录制
    mEncoder.encoderOnDraw(yTextureId, uTextureId, vTextureId, positionCords, textureCords);
}

-----------------------我是分割线------------------------------------------------------
// 注意对象,这是CodecEncoder
void CodecEncoder::encoderOnDraw(GLuint mYSamplerId, GLuint mUSamplerId, GLuint mVSamplerId,
                                float* positionCords, float* textureCords) {
    mWindowSurface->makeCurrent();
    //业务逻辑和GpuFilterRender没区别,只是EGL环境不一样。
    if(mCurrentTypeId!=mRequestTypeId) {
        // 更新filter
        if( mFilter!=NULL) {
            mFilter->destroy();
            delete mFilter;
            mFilter = NULL;
        }
        switch (mRequestTypeId)
        {
            case FILTER_TYPE_NORMAL: {
                mFilter = new GpuBaseFilter();
            }break;
            case FILTER_TYPE_CONTRAST:{
                mFilter = new GpuContrastFilter();
            }break;
            case FILTER_TYPE_COLOR_INVERT:{
                mFilter = new GpuColorInvertFilter();
            }break;
            case FILTER_TYPE_PIXELATION:{
                mFilter = new GpuPixelationFilter();
            }break;
            default:
                mFilter = new GpuBaseFilter();
                break;
        }
        mFilter->init();
        mFilter->onOutputSizeChanged(mWidth, mHeight);
        mCurrentTypeId = mRequestTypeId;
    }
    if( mFilter!=NULL) {
        mFilter->setAdjustEffect(mFilterEffectPercent);
        mFilter->onDraw(mYSamplerId, mUSamplerId, mVSamplerId, positionCords, textureCords);
    }
    mWindowSurface->swapBuffers();
}

到此,CodecEncoder的全部知识点分析完毕。

我们可以通过debugWriteOutputFile写下的h264/h265裸码流文件,用VLC / FFmpeg测试执行,你可能会发现显示出来的画面播放速度很快,一霎间就全部播放完了。  这是因为这只是简单的裸码流,解码器不知道对应的解码配置和pts等相关参数。这个只要把SPS/PPS传到解码器当中,并且设置好对应的PTS就OK。相关知识可以参照之前写的FFmpeg音视同步系列文章。

That is All.

项目路径:https://github.com/MrZhaozhirong/NativeCppApp

兴趣讨论群:703531738。暗号:志哥13567

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/105421686