文档管理中心
开发与测试开放能力API媒体AVCodec Kit(音视频编解码服务)音视频编解码开发实践基于Surface模式进行视频编码

基于Surface模式进行视频编码

概述

视频编码是指通过压缩技术,将原始视频数据转换成压缩数据的过程。HarmonyOS提供了媒体文件的解析和封装、音视频的编解码。基于Surface模式进行视频编码指通过NativeWindow来传递编码的输入数据,使用AVCodec Kit提供的视频编码能力实现视频编码的过程。开发者可以通过OHNativeWindow与其他模块进行对接,如相机模块、屏幕录制模块等。

实现原理

Surface轮转原理

在Surface模式下,视频编码依赖NativeWindow传递编码数据。其中,NativeWindow是本地平台化窗口,其作为Surface的生产者端,参与图形/媒体数据的共享传递。

Surface主要是用于管理、传递图形和媒体的共享内存,具体场景如图形的生产、消费、合成,媒体的播放、录制等。Surface通过共享内存的方式传递图形/媒体数据,避免了进程之间图形/媒体数据的拷贝,减少了进程之间数据传递的开销。

Surface分为生产者ProducerSurface和消费者ConsumerSurface。NativeWindow作为生产者ProducerSurface,进行图像/媒体数据的传递。

说明

相对于Buffer模式,Surface模式避免了进程之间的媒体数据拷贝,所以Surface模式的性能会更高。

Surface轮转流程如下所示,生产者先申请到一块Buffer,填充数据后将Buffer返回给BufferQueue。在触发回调函数后,通知消费者Buffer已经被生产者填充好数据。之后,消费者可以获取填充好数据的Buffer,直到不再需要该Buffer后,释放对应的Buffer。

视频编码器提供了获取NativeWindow的接口,通过NativeWindow可以将相机产生的数据与视频编码器进行对接。视频编码器作为消费者,将Buffer数据进行消费编码,从而实现视频编码的操作。下面我们将通过相机录制和屏幕录制,介绍基于Surface模式进行视频编码。

使用AVScreenCapture+AVCodec进行视频编码

场景描述

系统中提供了AVScreenCapture用于屏幕录制,AVScreenCapture可以支持屏幕录制并直接保存到视频文件中,还可以将录制的数据通过NativeWindow对接编码器进行数据编码。在基于Surface实现屏幕录制的方案中,开发者可以根据自己的需求保存对应的格式。

实现原理

Surface模式是通过NativeWindow包含的Surface传递录屏数据进行视频编码。在使用AVScreenCapture实现屏幕录制的场景中,开发者需要提前在编码器中获取NativeWindow对象,然后将获取的NativeWindow设置到AVScreenCapture中实现屏幕录制。具体开发步骤如下所示。

  1. 初始化视频封装器,创建并配置视频封装器。
  2. 初始化视频编码器,创建并配置视频编码器,并从编码器中获取NativeWindow对象。
  3. 初始化AVScreenCapture,创建并配置AVScreenCapture。
  4. 启动视频封装器。
  5. 启动视频编码器。
  6. 创建并启动编码输出子线程。
  7. 将从编码器中获取的NativeWindow对象设置给AVScreenCapture,启动屏幕录制。

开发步骤

  1. 初始化视频封装器,创建并配置视频封装器。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t Muxer::Create(int32_t fd) {
    2. muxer_ = OH_AVMuxer_Create(fd, AV_OUTPUT_FORMAT_MPEG_4);
    3. if (muxer_ == nullptr) {
    4. return -1;
    5. }
    6. return 0;
    7. }
    8. int32_t Muxer::Config(SampleInfo &sampleInfo) {
    9. OH_AVFormat *formatAudio = OH_AVFormat_CreateAudioFormat(sampleInfo.audioCodecMime.data(),
    10. sampleInfo.audioSampleRate, sampleInfo.audioChannelCount);
    11. OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_PROFILE, AAC_PROFILE_LC);
    12. int32_t ret = OH_AVMuxer_AddTrack(muxer_, &audioTrackId_, formatAudio);
    13. OH_AVFormat_Destroy(formatAudio);
    14. OH_AVFormat *formatVideo =
    15. OH_AVFormat_CreateVideoFormat(sampleInfo.videoCodecMime.data(), sampleInfo.videoWidth, sampleInfo.videoHeight);
    16. OH_AVFormat_SetDoubleValue(formatVideo, OH_MD_KEY_FRAME_RATE, sampleInfo.frameRate);
    17. OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_WIDTH, sampleInfo.videoWidth);
    18. OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight);
    19. OH_AVFormat_SetStringValue(formatVideo, OH_MD_KEY_CODEC_MIME, sampleInfo.videoCodecMime.data());
    20. ret = OH_AVMuxer_AddTrack(muxer_, &videoTrackId_, formatVideo);
    21. if (ret != AV_ERR_OK) {
    22. OH_LOG_ERROR(LOG_APP, "AddTrack failed");
    23. }
    24. OH_AVFormat_Destroy(formatVideo);
    25. formatVideo = nullptr;
    26. return ret;
    27. }
  2. 初始化视频编码器,创建并配置视频编码器,并从编码器中获取NativeWindow对象。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t VideoEncoder::Create(const std::string &videoCodecMime) {
    2. encoder_ = OH_VideoEncoder_CreateByMime(videoCodecMime.c_str());
    3. if (encoder_ == nullptr) {
    4. return -1;
    5. }
    6. return 0;
    7. }
    8. int32_t VideoEncoder::Config(SampleInfo &sampleInfo, CodecUserData *codecUserData) {
    9. OH_AVFormat *format = OH_AVFormat_Create();
    10. OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, sampleInfo.videoWidth);
    11. OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight);
    12. OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, sampleInfo.frameRate);
    13. OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, sampleInfo.pixelFormat);
    14. OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, sampleInfo.bitrateMode);
    15. OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, sampleInfo.bitrate);
    16. OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, sampleInfo.hevcProfile);
    17. int ret = OH_VideoEncoder_Configure(encoder_, format);
    18. if (ret != AV_ERR_OK) {
    19. OH_LOG_ERROR(LOG_APP, "Config failed, ret: %{public}d", ret);
    20. }
    21. OH_AVFormat_Destroy(format);
    22. format = nullptr;
    23. OH_VideoEncoder_GetSurface(encoder_, &sampleInfo.window);
    24. OH_VideoEncoder_RegisterCallback(encoder_,
    25. {VideoEncoder::OnCodecError, VideoEncoder::OnCodecFormatChange,
    26. VideoEncoder::OnNeedInputBuffer, VideoEncoder::OnNewOutputBuffer},
    27. codecUserData);
    28. OH_VideoEncoder_Prepare(encoder_);
    29. return 0;
    30. }
  3. 初始化AVScreenCapture,创建并配置AVScreenCapture。

    收起
    自动换行
    深色代码主题
    复制
    1. void CAVScreenCaptureToStream::InitAVScreenCapture(int32_t videoWidth,
    2. int32_t videoHeight) {
    3. if (g_avCapture != nullptr) {
    4. StopScreenCaptureRecording(g_avCapture);
    5. }
    6. g_avCapture = OH_AVScreenCapture_Create();
    7. if (g_avCapture == nullptr) {
    8. OH_LOG_ERROR(LOG_APP, "create screen capture failed");
    9. }
    10. OH_LOG_INFO(LOG_APP, "ScreenCapture after create sc");
    11. OH_AVScreenCapture_SetErrorCallback(g_avCapture, OnErrorToStream, nullptr);
    12. OH_AVScreenCapture_SetStateCallback(g_avCapture, OnSurfaceStateChangeToStream, nullptr);
    13. OH_AVScreenCapture_SetMicrophoneEnabled(g_avCapture, true);
    14. OH_AVScreenCapture_SetCanvasRotation(g_avCapture, true);
    15. OH_AVScreenCaptureConfig config;
    16. OH_AudioCaptureInfo micCapInfo = {.audioSampleRate = 48000, .audioChannels = 2, .audioSource = OH_SOURCE_DEFAULT};
    17. OH_AudioCaptureInfo innerCapInfo = {.audioSampleRate = 48000, .audioChannels = 2, .audioSource = OH_ALL_PLAYBACK};
    18. OH_AudioEncInfo audioEncInfo = {.audioBitrate = 96000, .audioCodecformat = OH_AudioCodecFormat::OH_AAC_LC};
    19. OH_AudioInfo audioInfo = {.micCapInfo = micCapInfo, .innerCapInfo = innerCapInfo, .audioEncInfo = audioEncInfo};
    20. OH_VideoCaptureInfo videoCapInfo = {
    21. .videoFrameWidth = videoWidth, .videoFrameHeight = videoHeight, .videoSource = OH_VIDEO_SOURCE_SURFACE_RGBA};
    22. OH_VideoEncInfo videoEncInfo = {
    23. .videoCodec = OH_VideoCodecFormat::OH_H264, .videoBitrate = 10000000, .videoFrameRate = 30};
    24. OH_VideoInfo videoInfo = {.videoCapInfo = videoCapInfo, .videoEncInfo = videoEncInfo};
    25. config = {
    26. .captureMode = OH_CAPTURE_HOME_SCREEN,
    27. .dataType = OH_ORIGINAL_STREAM,
    28. .audioInfo = audioInfo,
    29. .videoInfo = videoInfo,
    30. };
    31. int result = OH_AVScreenCapture_Init(g_avCapture, config);
    32. if (result != AV_SCREEN_CAPTURE_ERR_OK) {
    33. OH_LOG_INFO(LOG_APP, "ScreenCapture OH_AVScreenCapture_Init failed %{public}d", result);
    34. }
    35. OH_LOG_INFO(LOG_APP, "ScreenCapture OH_AVScreenCapture_Init %{public}d", result);
    36. }
  4. 启动视频封装器。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t Muxer::Start() {
    2. int ret = OH_AVMuxer_Start(muxer_);
    3. return ret;
    4. }
  5. 启动视频编码器。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t VideoEncoder::Start() {
    2. int ret = OH_VideoEncoder_Start(encoder_);
    3. return ret;
    4. }
  6. 创建并启动编码输出子线程。

    收起
    自动换行
    深色代码主题
    复制
    1. void CAVScreenCaptureToStream::EncOutputThread() {
    2. while (true) {
    3. if (!isStarted_) {
    4. OH_LOG_ERROR(LOG_APP, "Work done, thread out");
    5. break;
    6. }
    7. std::unique_lock<std::mutex> lock(videoEncContext_->outputMutex);
    8. bool condRet = videoEncContext_->outputCond.wait_for(
    9. lock, 5s, [this]() { return !isStarted_ || !videoEncContext_->outputBufferInfoQueue.empty(); });
    10. if (!isStarted_) {
    11. OH_LOG_ERROR(LOG_APP, "Work done, thread out");
    12. break;
    13. }
    14. if (videoEncContext_->outputBufferInfoQueue.empty()) {
    15. OH_LOG_ERROR(LOG_APP, "Buffer queue is empty, continue, cond ret: %{public}d", condRet);
    16. continue;
    17. }
    18. CodecBufferInfo bufferInfo = videoEncContext_->outputBufferInfoQueue.front();
    19. videoEncContext_->outputBufferInfoQueue.pop();
    20. if (bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS) {
    21. OH_LOG_ERROR(LOG_APP, "Catch EOS, thread out");
    22. break;
    23. }
    24. lock.unlock();
    25. if ((bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) ||
    26. (bufferInfo.attr.flags == AVCODEC_BUFFER_FLAGS_NONE)) {
    27. videoEncContext_->outputFrameCount++;
    28. if (lastFrameTimestampPts_ == 0) {
    29. lastFrameTimestampPts_ = bufferInfo.attr.pts;
    30. bufferInfo.attr.pts = 0;
    31. } else {
    32. lastFrameEncodePts_ += (bufferInfo.attr.pts - lastFrameTimestampPts_) / 1000;
    33. lastFrameTimestampPts_ = bufferInfo.attr.pts;
    34. bufferInfo.attr.pts = lastFrameEncodePts_;
    35. }
    36. } else {
    37. bufferInfo.attr.pts = 0;
    38. }
    39. muxer_->WriteSample(muxer_->GetVideoTrackId(), reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer),
    40. bufferInfo.attr);
    41. int32_t ret = videoEncoder_->FreeOutputBuffer(bufferInfo.bufferIndex);
    42. if (ret) {
    43. OH_LOG_ERROR(LOG_APP, "Encoder output thread out");
    44. break;
    45. }
    46. }
    47. StartRelease();
    48. OH_LOG_INFO(LOG_APP, "Exit, frame count: %{public}u", videoEncContext_->outputFrameCount);
    49. }
  7. 将从编码器中获取的NativeWindow对象设置给AVScreenCapture,启动屏幕录制。

    收起
    自动换行
    深色代码主题
    复制
    1. void CAVScreenCaptureToStream::StartScreenCapture(int32_t outputFd, int32_t videoWidth, int32_t videoHeight) {
    2. InitMuxerAndEncoder(outputFd, videoWidth, videoHeight);
    3. InitAVScreenCapture(videoWidth, videoHeight);
    4. m_IsRunning = true;
    5. StartMuxerAndEncoder();
    6. int result = OH_AVScreenCapture_StartScreenCaptureWithSurface(g_avCapture, sampleInfo_.window);
    7. OH_LOG_INFO(LOG_APP, "OH_VideoEncoder_Start Started 2 %{public}d", result);
    8. if (result != AV_SCREEN_CAPTURE_ERR_OK) {
    9. OH_LOG_INFO(LOG_APP, "ScreenCapture Started failed %{public}d", result);
    10. OH_AVScreenCapture_Release(g_avCapture);
    11. }
    12. }

使用Camera+AVCodec进行视频编码

场景描述

在相机录制的场景中,可以Camera相机对接AVRecorder实现录制,还可以通过Surface将相机与视频编码器进行对接,从而实现视频录制。在基于Surface实现相机录制的场景中,开发者可以自行配置对应的格式,如录制HDR视频。

实现原理

在使用Camera实现相机录制中,需要通过NativeWindow传递相机数据。开发者需要提前从视频编码器中获取NativeWindow,并从NativeWindow中获取对应的surfaceId。最后,在相机配置时,通过surfaceId创建相机输出流。具体开发步骤如下所示。

  1. 初始化视频封装器,创建并配置视频封装器。
  2. 初始化视频编码器,创建并配置视频编码器,从编码器中获取NativeWindow对象。从NativeWindow对象获取对应的surfaceId。
  3. 初始化相机配置,并通过createVideoOutput接口创建相机输出流。
  4. 在开启相机时,启动视频封装器。
  5. 启动视频编码器。
  6. 创建并启动编码输出子线程。

开发步骤

  1. 初始化视频封装器,创建并配置视频封装器。(此代码与屏幕录制一致)

  2. 初始化视频编码器,创建并配置视频编码器,从编码器中获取NativeWindow对象。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t VideoEncoder::GetSurface(SampleInfo &sampleInfo) {
    2. int32_t ret = OH_VideoEncoder_GetSurface(encoder_, &sampleInfo.window);
    3. CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK && sampleInfo.window, AVCODEC_SAMPLE_ERR_ERROR,
    4. "Get surface failed, ret: %{public}d", ret);
    5. return AVCODEC_SAMPLE_ERR_OK;
    6. }

    从NativeWindow对象获取对应的surfaceId。

    收起
    自动换行
    深色代码主题
    复制
    1. void NativeInit(napi_env env, void *data) {
    2. AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    3. int32_t ret = Recorder::GetInstance().Init(asyncCallbackInfo->sampleInfo);
    4. if (ret != AVCODEC_SAMPLE_ERR_OK) {
    5. SetCallBackResult(asyncCallbackInfo, -1);
    6. }
    7. uint64_t id = 0;
    8. ret = OH_NativeWindow_GetSurfaceId(asyncCallbackInfo->sampleInfo.window, &id);
    9. if (ret != AVCODEC_SAMPLE_ERR_OK) {
    10. SetCallBackResult(asyncCallbackInfo, -1);
    11. }
    12. asyncCallbackInfo->surfaceId = std::to_string(id);
    13. SurfaceIdCallBack(asyncCallbackInfo, asyncCallbackInfo->surfaceId);
    14. }
  3. 初始化相机配置,并通过createVideoOutput接口创建相机输出流。

    收起
    自动换行
    深色代码主题
    复制
    1. try {
    2. videoSession.addOutput(encoderVideoOutput);
    3. } catch (error) {
    4. let err = error as BusinessError;
    5. Logger.error(TAG, `Failed to add encoderVideoOutput. error: ${JSON.stringify(err)}`);
    6. }

    点击相机录制时,启动相机输出流。

    收起
    自动换行
    深色代码主题
    复制
    1. encoderVideoOutput.start((err: BusinessError) => {
    2. if (err) {
    3. Logger.error(TAG, `Failed to start the encoder video output. error: ${JSON.stringify(err)}`);
    4. return;
    5. }
    6. Logger.info(TAG, 'Callback invoked to indicate the encoder video output start success.');
    7. });
  4. 在开启相机时,启动视频封装器。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t Muxer::Start() {
    2. CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer is null");
    3. int ret = OH_AVMuxer_Start(muxer_);
    4. CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret);
    5. return AVCODEC_SAMPLE_ERR_OK;
    6. }
  5. 启动视频编码器。

    收起
    自动换行
    深色代码主题
    复制
    1. int32_t VideoEncoder::Start() {
    2. CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null");
    3. int ret = OH_VideoEncoder_Start(encoder_);
    4. CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret);
    5. return AVCODEC_SAMPLE_ERR_OK;
    6. }
  6. 创建并启动编码输出子线程。

    收起
    自动换行
    深色代码主题
    复制
    1. void Recorder::EncOutputThread() {
    2. while (true) {
    3. CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out");
    4. std::unique_lock<std::mutex> lock(encContext_->outputMutex);
    5. bool condRet = encContext_->outputCond.wait_for(
    6. lock, 5s, [this]() { return !isStarted_ || !encContext_->outputBufferInfoQueue.empty(); });
    7. CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out");
    8. CHECK_AND_CONTINUE_LOG(!encContext_->outputBufferInfoQueue.empty(),
    9. "Buffer queue is empty, continue, cond ret: %{public}d", condRet);
    10. CodecBufferInfo bufferInfo = encContext_->outputBufferInfoQueue.front();
    11. encContext_->outputBufferInfoQueue.pop();
    12. CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out");
    13. lock.unlock();
    14. if ((bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) ||
    15. (bufferInfo.attr.flags == AVCODEC_BUFFER_FLAGS_NONE)) {
    16. encContext_->outputFrameCount++;
    17. bufferInfo.attr.pts = encContext_->outputFrameCount * MICROSECOND / sampleInfo_.frameRate;
    18. } else {
    19. bufferInfo.attr.pts = 0;
    20. }
    21. AVCODEC_SAMPLE_LOGW("Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64,
    22. encContext_->outputFrameCount, bufferInfo.attr.size, bufferInfo.attr.flags,
    23. bufferInfo.attr.pts);
    24. muxer_->WriteSample(muxer_->GetVideoTrackId(), reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer),
    25. bufferInfo.attr);
    26. int32_t ret = videoEncoder_->FreeOutputBuffer(bufferInfo.bufferIndex);
    27. CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Encoder output thread out");
    28. }
    29. AVCODEC_SAMPLE_LOGI("Exit, frame count: %{public}u", encContext_->outputFrameCount);
    30. StartRelease();
    31. }

示例代码

在 开发与测试 开放能力API 中进行搜索
请输入您想要搜索的关键词