OpenHarmony VideoEncoder js接口开发

OpenHarmony 添加VideoEncoder的ts接口开发记录

一、背景

​ 由于项目中需要将camera导出的yuv数据进行推流,所以需要VideoEncoder来编码yuv数据程h264视频流,但是查看官方网站发现openharmony关于编码和解码这部分只有native接口。所以需要使用native接口实现视频编码并通过napi导出ts接口提供给app使用。而且在3.2release版本中 video encoder的native接口中没有buffer模式的入口,只能通过surface接口把数据传到video encoder中,而surface相关的接口并没有开放到ndk中。所以想要在3.2 release分支下开发video encoder的ts接口,只能将surface相关的接口导出到ndk中才能在app中开发。但是我在查看官方master分支中发现,在master分支上,官方将codec相管的接口都从/foundation/multimedia/player_framework/这个仓库移动到了/foundation/multimedia/av_codec/这个仓库下,并且增加了buffer模式的入口。,而且在3.2 release版本中虽然buffer模式的接口没有开放,但是底层其实还是实现了对应的接口的。所以现在有两个方案:

方案一、使用现成的surface接口,将surface相关的接口导出来即可开发

方案二、使用buffer接口,需要将最新的buffer模式的接口加入到目前的代码中

综合考虑我选择了方案二 项目地址:https://gitee.com/yw2219247615/camera-demo_-ohos

二、定义视频编码的接口

由于本人对于视频编码的知识了解较少,所以提供给app使用的接口主要参考native接口来设计,接口文件index.d.ts如下

//index.d.ts
import media from '@ohos.multimedia.media';

declare namespace avcodec {
    /**
     * Creates a video encoder instance.
     * @since 9
     * @syscap SystemCapability.Multimedia.Media.VideoEncoder
     * @return Returns VideoEncoder interface
     */
    function createVideoEncoder():VideoEncoder
    /**
     * Manages video encoder. Before calling an VideoEncoder method, you must use createVideoEncoder()
     * to create an VideoEncoder instance.
     * @since 9
     * @syscap SystemCapability.Multimedia.Media.VideoEncoder
     */
    interface VideoEncoder {
        /**
         * To prepare the internal resources of the encoder, the Configure interface must be called before
         * calling this interface.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        prepare():CodecErrCode
        /**
         * To configure the video encoder, typically, you need to configure the description information of the
         * encoded video track. This interface must be called before Prepare is called.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @param  an AvFormat instance {@link AvFormat}
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        configure(format:AvFormat):CodecErrCode
        /**
         * Start the encoder, this interface must be called after the Prepare is successful.
         * calling this interface.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        start():CodecErrCode
        /**
         * Stop the encoder. After stopping, you can re-enter the Started state through Start.
         * calling this interface.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        stop():CodecErrCode
        /**
         * Clear output data buffered in the encoder. After this interface is called, all the Buffer
         * indexes previously reported through the asynchronous callback will be invalidated, make sure not to access the
         * Buffers corresponding to these indexes. this interface must be called before stop interface.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        flush():CodecErrCode
        /**
         * Reset the encoder. To continue coding, you need to call the configure interface again to
         * configure the encoder instance.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        reset():CodecErrCode
        /**
         * Get the description information of the output data of the encoder, refer to {@link AvFormat} for details.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns a string obj about AvFormat
         */
        getOutputDescription():string
        /**
         * Clear the internal resources of the encoder and destroy the encoder instance
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @return Returns AV_ERR_OK if the execution is successful,
         * otherwise returns a specific error code, refer to {@link CodecErrCode}
         */
        release():CodecErrCode
        /**
         * Send frame data to video encoder, this interface must be called after the start is successful.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @param data Image data buffer
         * @param dataSize Image data byte size, Indicates the size of valid data in the buffer
         * @param timeStamp Image data timestamp
         */
        sendFrameData(data:Uint8Array,dataSize:number,timeStamp:number):void
        /**
         * Register or unregister listens for video encoder events.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @param type Type of the video encoder event to listen for.
         * @param callback Callback used to listen for the video encoder output buffer event.
         */
        on(type:"OnOutputBufferAvailable",callback:(data: ArrayBuffer, dataSize: number,pts:number,flags:number) => void):void
        /**
         * Register or unregister listens for video encoder events.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @param type Type of the video encoder event to listen for.
         * @param callback Callback used to listen for the video encoder error event.
         */
        on(type:"onError",callback:(errCoder: number) => void):void
        /**
         * Register or unregister listens for video encoder events.
         * @since 9
         * @syscap SystemCapability.Multimedia.Media.VideoEncoder
         * @param type Type of the video encoder event to listen for.
         * @param callback Callback used to listen for the video encoder output format change event.
         */
        on(type:"OnOutputFormatChanged",callback:(format: AvFormat) => void):void
    }

    interface AvFormat {
        /**设置编码器的类型目前只支持视频编码器*/
        mime:media.CodecMimeType;
        /**图像帧的宽度 */
        width:number;
        /**图像帧的高度 */
        height:number;
        /**输入图像的格式 */
        pixelFormat:VideoPixelFormat;
        /**图像编码的帧率*/
        frameRate:number;
        /**图像编码的码率*/
        encodeBitRate:number;
        /** I 帧采集的间隔 单位:毫秒*/
        iFrameIntervalMs:number;
        /**编码质量:0 - 100*/
        codecQuality:number;
        /**编码的profile:
         *AVC_PROFILE_BASELINE = 0,
         *AVC_PROFILE_HIGH = 4,
         *AVC_PROFILE_MAIN = 8,
         */
        codecProfile:number;

        videoEncodeBitRateMode: VideoEncodeBitrateMode;
    }

    enum VideoPixelFormat {
        /**
         * yuv 420 planar.
         */
        YUVI420 = 1,
        /**
         *  NV12. yuv 420 semiplanar.
         */
        NV12 = 2,
        /**
         *  NV21. yvu 420 semiplanar.
         */
        NV21 = 3,
        /**
         * format from surface.
         */
        SURFACE_FORMAT = 4,
        /**
         * RGBA.
         */
        RGBA = 5,
    }

    enum CodecErrCode {
        /**
         * the operation completed successfully.
         */
        AV_ERR_OK = 0,
        /**
         * no memory.
         */
        AV_ERR_NO_MEMORY = 1,
        /**
         * opertation not be permitted.
         */
        AV_ERR_OPERATE_NOT_PERMIT = 2,
        /**
         * invalid argument.
         */
        AV_ERR_INVALID_VAL = 3,
        /**
         * IO error.
         */
        AV_ERR_IO = 4,
        /**
         * network timeout.
         */
        AV_ERR_TIMEOUT = 5,
        /**
         * unknown error.
         */
        AV_ERR_UNKNOWN = 6,
        /**
         * media service died.
         */
        AV_ERR_SERVICE_DIED = 7,
        /**
         * the state is not support this operation.
         */
        AV_ERR_INVALID_STATE = 8,
        /**
         * unsupport interface.
         */
        AV_ERR_UNSUPPORT = 9,
        /**
         * extend err start.
         */
        AV_ERR_EXTEND_START = 100,
    }

    enum VideoEncodeBitrateMode {
        /**
         * constant bit rate mode.
         */
        CBR = 0,
        /**
         * variable bit rate mode.
         */
        VBR = 1,
        /**
         * constant quality mode.
         */
        CQ = 2,
    }
}

export default avcodec;

上面的文件主要时定义了一些encoder的行为和相关的枚举类,基本也都是从native层拷贝过来的,AvFormat主要用来配置videoencoder的参数。

三、建立NAPI层

这里我定义VideoEncoder的napi层为video_encoder_napi.cpp

首先需要先定义和上层ts接口对应的napi接口如下:

//video_encoder_napi.cpp
napi_value VideoEncoderNapi::Init(napi_env env, napi_value exports) {
    AVCODEC_LOGD("%s ", __FUNCTION__);
    napi_property_descriptor properties[] = {
        //这里的这些宏定义都被我导入到了common/native_common.h中其实都是系统源码中的
        DECLARE_NAPI_FUNCTION("configure", Configure),
        DECLARE_NAPI_FUNCTION("prepare", Prepare),
        DECLARE_NAPI_FUNCTION("start", Start),
        DECLARE_NAPI_FUNCTION("stop", Stop),
        DECLARE_NAPI_FUNCTION("flush", Flush),
        DECLARE_NAPI_FUNCTION("reset", Reset),
        DECLARE_NAPI_FUNCTION("getOutputDescription", GetOutputDescription),
        DECLARE_NAPI_FUNCTION("sendFrameData", SendFrameData),
        DECLARE_NAPI_FUNCTION("release", Release),
        DECLARE_NAPI_FUNCTION("on", On)};

    
    napi_property_descriptor staticProperty[] = {
        DECLARE_NAPI_STATIC_FUNCTION("createVideoEncoder", CreateVideoEncoder),
    };

    napi_value constructor = nullptr;
    napi_status status = napi_define_class(env, CLASS_NAME.c_str(), NAPI_AUTO_LENGTH, Constructor, nullptr,
                                           sizeof(properties) / sizeof(properties[0]), properties, &constructor);
    CHECK_AND_RETURN_RET_LOG(status == napi_ok, nullptr, "Failed to define VideoEncoder class");

    status = napi_create_reference(env, constructor, 1, &constructor_);
    CHECK_AND_RETURN_RET_LOG(status == napi_ok, nullptr, "Failed to create reference of constructor");

    status = napi_set_named_property(env, exports, CLASS_NAME.c_str(), constructor);
    CHECK_AND_RETURN_RET_LOG(status == napi_ok, nullptr, "Failed to set constructor");

    status = napi_define_properties(env, exports, sizeof(staticProperty) / sizeof(staticProperty[0]), staticProperty);
    CHECK_AND_RETURN_RET_LOG(status == napi_ok, nullptr, "Failed to define static function");

    AVCODEC_LOGD("Init success");

    return exports;
}

在上面的接口中CreateVideoEncoder会触发整个video_encoder_napi的创建,当调用configure时才会通过上层传下来的avformat中的mime真正的创建encoder,并配置相关的参数,剩下的操作基本就是一个搬运工将ts的指令传递到native的encoder即可。

3.1 CreateVideoEncoder

napi_value VideoEncoderNapi::CreateVideoEncoder(napi_env env, napi_callback_info info) {
    AVCODEC_LOGD("%s ", __FUNCTION__);
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    //创建napi对象
    napi_value ctor;
    napi_status status;
    status = napi_get_reference_value(env, constructor_, &ctor);
    if (status != napi_ok) {
        AVCODEC_LOGE("napi_get_reference_value fail");
        return result;
    }
    status = napi_new_instance(env, ctor, 0, nullptr, &result);
    if (status == napi_ok) {
        AVCODEC_LOGD("New instance success");
    } else {
        AVCODEC_LOGD("New instance could not be obtained");
    }
    return result;
}

CreateVideoEncoder函数非常简单就是创建napi对象,其中会调用到VideoEncoderNapi::Constructor函数,如下;

napi_value VideoEncoderNapi::Constructor(napi_env env, napi_callback_info info) {
    AVCODEC_LOGD("%s ", __FUNCTION__);
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    napi_value jsThis = nullptr;
    napi_status status = napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);
    if (status != napi_ok) {
        AVCODEC_LOGE("Failed to retrieve details about the callback");
        return result;
    }
    if (jsThis != nullptr) {
        VideoEncoderNapi *videoEncoderNapi = new (std::nothrow) VideoEncoderNapi();
        if (videoEncoderNapi != nullptr) {
            videoEncoderNapi->env_ = env;
            status = napi_wrap(env, jsThis, reinterpret_cast<void *>(videoEncoderNapi),
                               VideoEncoderNapi::Destructor, nullptr, nullptr);
            if (status != napi_ok) {
                delete videoEncoderNapi;
                AVCODEC_LOGE("Failed to warp native instance!");
                return result;
            }
        }
    }
    AVCODEC_LOGD("Constructor success");
    return jsThis;
}

上面的这些基本都属于模板了。

3.2 Configure

napi_value VideoEncoderNapi::Configure(napi_env env, napi_callback_info info) {
    AVCODEC_LOGD("%s ", __FUNCTION__);
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    //获取arkts传过来参数信息
    napi_value thisVar = nullptr;
    napi_value args[ARGS1] = {nullptr};
    size_t argCount = ARGS1;
    napi_status status;
    status = napi_get_cb_info(env, info, &argCount, args, &thisVar, nullptr);
    CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "failed to napi_get_cb_info");
    //获取AvFormat
    AvFormat format = getAvFormat(env, args[PARAM0]);
    //获取napi实例
    VideoEncoderNapi *videoEncoderNapi = nullptr;
    getVideoEncoderNapi(env, thisVar, &videoEncoderNapi);
    if (videoEncoderNapi == nullptr)
        return result;
    int32_t ret;
    videoEncoderNapi->videoEncoder = VideoEncoderFactory::CreateByMime(format.codec_mime);
    ret = videoEncoderNapi->videoEncoder->configure(&format);
    //创建回调接口
    videoEncoderNapi->codecCallback = std::make_shared<AvCodecCallbackNapi>();
    videoEncoderNapi->videoEncoder->SetCallback(videoEncoderNapi->codecCallback);
    AvCodecNapiUtils::createNapiInt32Value(env, ret, result);
    return result;
}

在napi层当调用了configure后才会真正创建native层的video encoder可以看到上面通过调用VideoEncoderFactory::CreateByMime创建一个videoEncoder对象并保存在了videoEncoderNapi的videoEncoder指针里,后面我们的操作都会通过这个指针进行。

3.3 Start

napi_value VideoEncoderNapi::Start(napi_env env, napi_callback_info info) {
    AVCODEC_LOGD("%s ", __FUNCTION__);
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    napi_get_undefined(env, &result);
    napi_status status;
    AVCODEC_NAPI_GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "failed to napi_get_cb_info");
    VideoEncoderNapi *videoEncoderNapi = nullptr;
    getVideoEncoderNapi(env, thisVar, &videoEncoderNapi);
    if (videoEncoderNapi == nullptr)
        return result;
    int32_t ret;
    ret = videoEncoderNapi->videoEncoder->start();
    AvCodecNapiUtils::createNapiInt32Value(env, ret, result);
    return result;
}

可以看到正如上面所说都是通过获取napi实例,接着通过它的videoEncoder来操作video encoder的,其他的流程控制比如stop reset release flush等等都是一个模板。这里就不再细说了

四、封装video encoder

由于video encoder的ndk中都只是一些接口,所以我这边将这些接口封装成了一个类VideoEncoder,在上面的3.2小节中我们知道videoencoder是通过VideoEncoderFactory创建的,当它初始化时就会创建native video encoder并设置callback

VideoEncoder::VideoEncoder(std::string mime){
    AVCODEC_LOGD("VideoEncoder create mime =%s",mime.c_str());
    venc_ = OH_VideoEncoder_CreateByMime(mime.c_str());
    if(venc_ == nullptr){
        AVCODEC_LOGD("venc_ is null");
    }
    setEncoderCallback();
    setVideoEncoderMap(venc_,this);
}
int32_t VideoEncoder::setEncoderCallback(){
    AVCODEC_CHECK_AND_RETURN_RET_LOG(venc_ != nullptr, AV_ERR_UNKNOWN,"Fatal: OH_VideoEncoder_CreateByMime fail");
    struct OH_AVCodecAsyncCallback callback;
                callback.onError = VideoEncoder::OnError;
                callback.onStreamChanged = VideoEncoder::OnStreamChanged;
                callback.onNeedInputData = VideoEncoder::OnNeedInputData;
                callback.onNeedOutputData = VideoEncoder::OnNewOutputData;
    return OH_VideoEncoder_SetCallback(venc_, callback,nullptr);
}
void VideoEncoder::setVideoEncoderMap(OH_AVCodec* codec,VideoEncoder* encoder){
    std::lock_guard<std::mutex> lock(mutex_);
    encoderMap_[codec] = encoder;
}

setEncoderCallback主要是设置一些接口给native video encoder来接收输入的buffer(用来填充待编码的数据)和已经编码的buffer数据以及一些错误和变化。setVideoEncoderMap则是将当前创建的native vidoe encoder和我们的VideoEncoder对象对应起来,这是因为OH_AVCodecAsyncCallback的成员函数要求是静态函数,但是如果我们的VideoEncoder对象创建多个,那么无法找到对应的VideoEncoder对象去回调,所以我这边使用了一个map去存储对应关系,这个map的key是OH_AVCodec指针,value是VideoEncoder指针。

4.1 数据流转

当video encoder启动后底层会回调待填充数据的buffer上来,我这里会将这个buffer存储到一个队列中,当上层ts传入数据下来时,我从这个队列中拿出一个buffer将数据拷贝进去,接着调用OH_VideoEncoder_PushInputData将数据发送给native video encoder。代码实现如下:
//native video encoder回调可用的buffer
void VideoEncoder::OnNeedInputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, void *userData){
    //从map中获取到encoder对象
    VideoEncoder* encoder = GetVideoEncoder(codec);
    if (encoder != nullptr) {
        //将buffer存放到队列中
        encoder->pushInputBufferToQueue(index,data);
    }
}

int32_t VideoEncoder::sendFrameData(uint8_t *data, int32_t size, int32_t timestamp){
    //当上层的待编码的数据传过来时
    AVCODEC_LOGD("sendFrameData data = %d",data);
    AVCODEC_CHECK_AND_RETURN_RET_LOG(venc_ != nullptr, AV_ERR_UNKNOWN,"Fatal: OH_VideoEncoder_CreateByMime fail");
    std::lock_guard<std::mutex> lock(queueMutex_);
    if(indexQueue.size() <= 0){
        AVCODEC_LOGE("indexQueue is empty");
        return AV_ERR_NO_MEMORY;
    }
    uint32_t index = indexQueue.front();
    auto buffer = inBufferQueue.front();
    //从队列中拿出buffer
    OH_AVCodecBufferAttr info;
    if(size == 0){
        info.size = 0;
        info.offset = 0;
        info.pts = 0;
        info.flags = AVCODEC_BUFFER_FLAGS_EOS;
        OH_VideoEncoder_PushInputData(venc_, index, info);
        AVCODEC_LOGE("send data len == 0");
    }
    info.size = size;
    info.offset = 0;
    info.pts = timestamp;
    //拷贝数据
    memcpy(OH_AVMemory_GetAddr(buffer), data, OH_AVMemory_GetSize(buffer));
    //将buffer 推给native video encoder去编码 //这里调用的是buffer模式的发送数据3.2 release分支需要增加此接口并导出
    int32_t ret = OH_VideoEncoder_PushInputData(venc_,index,info);
    indexQueue.pop();
    inBufferQueue.pop();
    return ret;
}

当数据编码后会通过VideoEncoder::OnNewOutputData接收到

void VideoEncoder::OnNewOutputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data,OH_AVCodecBufferAttr *attr, void *userData){
    VideoEncoder* encoder = GetVideoEncoder(codec);
    if (encoder != nullptr) {
        encoder->avcodecCb->OnOutputBufferAvailable(OH_AVMemory_GetAddr(data), attr->size, attr->pts, attr->flags);
    }
    OH_VideoEncoder_FreeOutputData(codec,index);
}

这里通过回调接口将数据回调到上层后我们需要调用OH_VideoEncoder_FreeOutputData将buffer归还给native video encoder来维持数据流转。

五、增加buffer模式的发送数据接口

From 72ee572a520604d23dd2aee9064d4fc252462df4 Mon Sep 17 00:00:00 2001
From: yuwei <yuw@guideir.com>
Date: Fri, 2 Jun 2023 11:06:36 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0video=20encoder=20buffer?=
 =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=B9=B6=E5=AF=BC=E5=87=BAndk=E6=8E=A5?=
 =?UTF-8?q?=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../capi/avcodec/native_video_encoder.cpp     | 67 ++++++++++++++++++-
 .../kits/c/native_avcodec_videoencoder.h      | 27 ++++++++
 .../libnative_media_venc.ndk.json             |  4 +-
 .../codec/hdi_plugins/hdi_codec_util.cpp      |  2 +
 4 files changed, 98 insertions(+), 2 deletions(-)

diff --git a/player_framework/frameworks/native/capi/avcodec/native_video_encoder.cpp b/player_framework/frameworks/native/capi/avcodec/native_video_encoder.cpp
index 07b9989..8ac87ea 100644
--- a/player_framework/frameworks/native/capi/avcodec/native_video_encoder.cpp
+++ b/player_framework/frameworks/native/capi/avcodec/native_video_encoder.cpp
@@ -39,10 +39,12 @@ struct VideoEncoderObject : public OH_AVCodec {
     const std::shared_ptr<AVCodecVideoEncoder> videoEncoder_;
     std::list<OHOS::sptr<OH_AVMemory>> memoryObjList_;
     OHOS::sptr<OH_AVFormat> outputFormat_ = nullptr;
+    OHOS::sptr<OH_AVFormat> inputputFormat_ = nullptr;
     std::shared_ptr<NativeVideoEncoderCallback> callback_ = nullptr;
     std::atomic<bool> isFlushing_ = false;
     std::atomic<bool> isStop_ = false;
     std::atomic<bool> isEOS_ = false;
+    bool isInputSurfaceMode_ = false;
 };
 
 class NativeVideoEncoderCallback : public AVCodecCallback {
@@ -82,7 +84,12 @@ public:
                 MEDIA_LOGD("At flush, eos or stop, no buffer available");
                 return;
             }
-            callback_.onNeedInputData(codec_, index, nullptr, userData_);
+            OH_AVMemory *data = nullptr;
+            if (!videoEncObj->isInputSurfaceMode_) {
+                data = GetInputData(codec_, index);
+                CHECK_AND_RETURN_LOG(data != nullptr, "Data is nullptr, get input data failed");
+            }
+            callback_.onNeedInputData(codec_, index, data, userData_);
         }
     }
 
@@ -115,6 +122,30 @@ public:
     }
 
 private:
+    OH_AVMemory *GetInputData(struct OH_AVCodec *codec, uint32_t index)
+    {
+        CHECK_AND_RETURN_RET_LOG(codec != nullptr, nullptr, "Codec is nullptr!");
+        CHECK_AND_RETURN_RET_LOG(codec->magic_ == AVMagic::MEDIA_MAGIC_VIDEO_ENCODER, nullptr, "Codec magic error!");
+
+        struct VideoEncoderObject *videoEncObj = reinterpret_cast<VideoEncoderObject *>(codec);
+        CHECK_AND_RETURN_RET_LOG(videoEncObj->videoEncoder_ != nullptr, nullptr, "Video decoder is nullptr!");
+
+        std::shared_ptr<AVSharedMemory> memory = videoEncObj->videoEncoder_->GetInputBuffer(index);
+        CHECK_AND_RETURN_RET_LOG(memory != nullptr, nullptr, "Memory is nullptr, get input buffer failed!");
+
+        for (auto &memoryObj : videoEncObj->memoryObjList_) {
+            if (memoryObj->IsEqualMemory(memory)) {
+                return reinterpret_cast<OH_AVMemory *>(memoryObj.GetRefPtr());
+            }
+        }
+
+        OHOS::sptr<OH_AVMemory> object = new (std::nothrow) OH_AVMemory(memory);
+        CHECK_AND_RETURN_RET_LOG(object != nullptr, nullptr, "AV memory create failed");
+
+        videoEncObj->memoryObjList_.push_back(object);
+        return reinterpret_cast<OH_AVMemory *>(object.GetRefPtr());
+    }
+
     OH_AVMemory *GetOutputData(struct OH_AVCodec *codec, uint32_t index)
     {
         CHECK_AND_RETURN_RET_LOG(codec != nullptr, nullptr, "input codec is nullptr!");
@@ -319,6 +350,7 @@ OH_AVErrCode OH_VideoEncoder_GetSurface(OH_AVCodec *codec, OHNativeWindow **wind
     *window = CreateNativeWindowFromSurface(&surface);
 
     CHECK_AND_RETURN_RET_LOG(*window != nullptr, AV_ERR_INVALID_VAL, "CreateNativeWindowFromSurface failed!");
+    videoEncObj->isInputSurfaceMode_ = true;
 
     return AV_ERR_OK;
 }
@@ -402,3 +434,36 @@ OH_AVErrCode OH_VideoEncoder_SetCallback(
 
     return AV_ERR_OK;
 }
+OH_AVErrCode OH_VideoEncoder_PushInputData(struct OH_AVCodec *codec, uint32_t index, OH_AVCodecBufferAttr attr)
+{
+    CHECK_AND_RETURN_RET_LOG(codec != nullptr, AV_ERR_INVALID_VAL, "Codec is nullptr!");
+    CHECK_AND_RETURN_RET_LOG(codec->magic_ == AVMagic::MEDIA_MAGIC_VIDEO_ENCODER, AV_ERR_INVALID_VAL,
+        "Codec magic error!");
+    CHECK_AND_RETURN_RET_LOG(attr.size >= 0, AV_ERR_INVALID_VAL, "Invalid buffer size!");
+
+    struct VideoEncoderObject *videoEncObj = reinterpret_cast<VideoEncoderObject *>(codec);
+    CHECK_AND_RETURN_RET_LOG(videoEncObj->videoEncoder_ != nullptr, AV_ERR_INVALID_VAL, "Video encoder is nullptr!");
+
+    struct AVCodecBufferInfo bufferInfo;
+    bufferInfo.presentationTimeUs = attr.pts;
+    bufferInfo.size = attr.size;
+    bufferInfo.offset = attr.offset;
+    enum AVCodecBufferFlag bufferFlag = static_cast<enum AVCodecBufferFlag>(attr.flags);
+
+    int32_t ret = videoEncObj->videoEncoder_->QueueInputBuffer(index, bufferInfo, bufferFlag);
+    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, AV_ERR_OPERATE_NOT_PERMIT, "Video encoder push input data failed!");
+    if (bufferFlag == AVCODEC_BUFFER_FLAG_EOS) {
+        videoEncObj->isEOS_.store(true);
+    }
+    return AV_ERR_OK;
+}
+
+OH_AVErrCode OH_VideoEncoder_IsValid(OH_AVCodec *codec, bool *isValid)
+{
+    CHECK_AND_RETURN_RET_LOG(codec != nullptr, AV_ERR_INVALID_VAL, "Codec is nullptr!");
+    CHECK_AND_RETURN_RET_LOG(codec->magic_ == AVMagic::MEDIA_MAGIC_VIDEO_ENCODER, AV_ERR_INVALID_VAL,
+        "Codec magic error!");
+    CHECK_AND_RETURN_RET_LOG(isValid != nullptr, AV_ERR_INVALID_VAL, "Input isValid is nullptr!");
+    *isValid = true;
+    return AV_ERR_OK;
+}
diff --git a/player_framework/interfaces/kits/c/native_avcodec_videoencoder.h b/player_framework/interfaces/kits/c/native_avcodec_videoencoder.h
index a429309..2610f00 100644
--- a/player_framework/interfaces/kits/c/native_avcodec_videoencoder.h
+++ b/player_framework/interfaces/kits/c/native_avcodec_videoencoder.h
@@ -207,6 +207,33 @@ OH_AVErrCode OH_VideoEncoder_FreeOutputData(OH_AVCodec *codec, uint32_t index);
  */
 OH_AVErrCode OH_VideoEncoder_NotifyEndOfStream(OH_AVCodec *codec);
 
+/**
+ * @brief Submit the input buffer filled with data to the video encoder.
+ * @syscap SystemCapability.Multimedia.Media.VideoEncoder
+ * @param codec Pointer to an OH_AVCodec instance
+ * @param index Enter the index value corresponding to the Buffer
+ * @param attr Information describing the data contained in the Buffer
+ * @return Returns AV_ERR_OK if the execution is successful,
+ * otherwise returns a specific error code, refer to {@link OH_AVErrCode}
+ * @since 10
+ * @version 4.0
+ */
+OH_AVErrCode OH_VideoEncoder_PushInputData(OH_AVCodec *codec, uint32_t index, OH_AVCodecBufferAttr attr);
+
+/**
+ * @brief Check whether the current codec instance is valid. It can be used fault recovery or app
+ * switchback from the background
+ * @syscap SystemCapability.Multimedia.Media.VideoEncoder
+ * @param codec Pointer to an OH_AVCodec instance
+ * @param isValid Pointer to an bool instance, true: the codec instance is valid, false: the codec
+ * instance is invalid
+ * @return Returns AV_ERR_OK if the execution is successful,
+ * otherwise returns a specific error code, refer to {@link OH_AVErrCode}
+ * @since 10
+ * @version 4.0
+ */
+OH_AVErrCode OH_VideoEncoder_IsValid(OH_AVCodec *codec, bool *isValid);
+
 /**
  * @brief The bitrate mode of video encoder.
  * @syscap SystemCapability.Multimedia.Media.VideoEncoder
diff --git a/player_framework/interfaces/kits/c/video_encoder/libnative_media_venc.ndk.json b/player_framework/interfaces/kits/c/video_encoder/libnative_media_venc.ndk.json
index e2264a4..a62e0c1 100644
--- a/player_framework/interfaces/kits/c/video_encoder/libnative_media_venc.ndk.json
+++ b/player_framework/interfaces/kits/c/video_encoder/libnative_media_venc.ndk.json
@@ -13,5 +13,7 @@
     { "name": "OH_VideoEncoder_SetParameter" },
     { "name": "OH_VideoEncoder_GetSurface" },
     { "name": "OH_VideoEncoder_FreeOutputData" },
-    { "name": "OH_VideoEncoder_NotifyEndOfStream" }
+    { "name": "OH_VideoEncoder_NotifyEndOfStream" },
+    { "name": "OH_VideoEncoder_PushInputData" },
+    { "name": "OH_VideoEncoder_IsValid" }
 ]

2.25.1


六、存在的问题

1.在持续的编码的数据过程中发现内存一直在持续增加,可能出现了内存泄漏

加入官方的patch:https://gitee.com/openharmony/arkui_napi/commit/420a9434606c2b1b6be726eb1b2f6c34b2faff94

并且加入如下的代码

--- a/player_framework/frameworks/js/avcodec/avcodec_callback_napi.cpp
+++ b/player_framework/frameworks/js/avcodec/avcodec_callback_napi.cpp
@@ -38,6 +38,9 @@ void AvCodecCallbackNapi::OnOutputBufferAvailableAsync(BufferInfo &info) const{
     int32_t flags = info.flags;
     if(onOutputBufferAvailableCb_env == nullptr || onOutputBufferAvailableCb == nullptr) return;
     if(data != nullptr && size > 0){
+        napi_handle_scope scope;
+        napi_open_handle_scope(onOutputBufferAvailableCb_env,&scope);
+        if(scope == nullptr) return;
         napi_value args[ARGS4] = {0};
         napi_create_int32(onOutputBufferAvailableCb_env,size,&args[PARAM1]);
         napi_create_int32(onOutputBufferAvailableCb_env,pts,&args[PARAM2]);
@@ -50,6 +53,7 @@ void AvCodecCallbackNapi::OnOutputBufferAvailableAsync(BufferInfo &info) const{
             AVCODEC_LOGE("AvCodecCallbackNapi::%s CreateArrayBuffer fail",__FUNCTION__);
             free(napiArrBuf);
             napiArrBuf = nullptr;
+            napi_close_handle_scope(onOutputBufferAvailableCb_env, scope);
             return;
         }
         napi_value callbackRet = nullptr;
@@ -59,6 +63,7 @@ void AvCodecCallbackNapi::OnOutputBufferAvailableAsync(BufferInfo &info) const{
             AVCODEC_LOGE("AvCodecCallbackNapi::%s napi_get_reference_value err[%{public}d]",__FUNCTION__, status);
             free(napiArrBuf);
             napiArrBuf = nullptr;
+            napi_close_handle_scope(onOutputBufferAvailableCb_env, scope);
             return;
         }
         status = napi_call_function(onOutputBufferAvailableCb_env, nullptr, callback, ARGS4, args, &callbackRet);
@@ -69,6 +74,7 @@ void AvCodecCallbackNapi::OnOutputBufferAvailableAsync(BufferInfo &info) const{
         }
         free(napiArrBuf);
         napiArrBuf = nullptr;
+        napi_close_handle_scope(onOutputBufferAvailableCb_env, scope);
     }
 }

可以降低内存增加的速度,但是无法彻底解决内存增长的问题,目前怀疑是napi层的接口存在内存泄漏已经向官方提了问题单,需要持续跟踪。
2023.07.26 拉取openharmony的master分支编译验证发现此问题已经解决。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值