NDK中实现AVScreenCapture在Surface模式下回调编码数据到ArkTS侧
=============================================

#### 问题现象

如何在NDK开发中使用AVScreenCapture实时录制屏幕，并将Surface模式实时编码数据一帧一帧的回调到ArkTS侧？

#### 背景知识

*   [AVScreenCapture录屏取码流](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/using-avscreencapture-for-buffer)主要应用于屏幕录制、会议共享、直播等场景。根据录屏码流的获取方式，可分为buffer和Surface模式。buffer模式获取音视频原始码流，然后通过流的方式流转到其他模块处理。Surface对接NativeImage作为消费者端，提供Surface关联OpenGL外部纹理。
*   [线程安全函数](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-thread-safety)是Node-API接口之一，用于创建一个线程安全的JavaScript函数。主要用于在多个线程之间共享和调用，而不会出现竞争条件或死锁。

#### 解决方案

1.  实现录屏和回调函数。
    
    _/\*_
     _\* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved._
     _\*/_
    #include "napi/native\_api.h"
    #include "sample\_info.h"
    #include "Recorder.h"
    
    #include <multimedia/player\_framework/native\_avcodec\_base.h\>
    #include <multimedia/player\_framework/native\_avscreen\_capture.h\>
    #include <multimedia/player\_framework/native\_avscreen\_capture\_base.h\>
    #include <multimedia/player\_framework/native\_avbuffer.h\>
    #include <multimedia/player\_framework/native\_avcodec\_videoencoder.h\>
    #include <multimedia/player\_framework/native\_avcapability.h\>
    #include <multimedia/player\_framework/native\_avcodec\_videoencoder.h\>
    
     _ // 错误事件发生回调函数OnError()。_
      void OnError(OH\_AVScreenCapture \*capture, int32\_t errorCode, void \*userData) {
      (void)capture;
      _// 应用根据错误码进行事件处理。_
      (void)errorCode;
      (void)userData;
    }
    
    _//_ _状态变更事件处理函数OnStateChange()。_
    void OnStateChange(struct OH\_AVScreenCapture \*capture, OH\_AVScreenCaptureStateCode stateCode, void \*userData) {
      (void)capture;
      if (stateCode \== OH\_AVScreenCaptureStateCode::OH\_SCREEN\_CAPTURE\_STATE\_CANCELED) { _// 按照所需状态自行修改填写。_
       _ // 处理录屏状态变更。_
      }
      (void)userData;
    }
    
    _//_ _获取并处理音视频原始码流数据回调函数OnBufferAvailable()。_
    void OnBufferAvailable(OH\_AVScreenCapture \*capture, OH\_AVBuffer \*buffer, OH\_AVScreenCaptureBufferType bufferType,
    int64\_t timestamp, void \*userData) {
     _ // 处于录屏取码流状态。_
    }
    
    _//_ _执行回调ArkTS函数，将编码后视频帧回调ArkTS_
    static void callJS(napi\_env env, napi\_value js\_cb, void \*context, void \*data) {
      SampleInfo \*sampleInfo \= reinterpret\_cast<SampleInfo \*\>(data);
      napi\_value buffer;
      napi\_create\_buffer(env, sizeof(sampleInfo\-\>buf), reinterpret\_cast<void \*\*\>(&sampleInfo\-\>buf), &buffer);
      napi\_value callFunc \= nullptr;
      napi\_get\_reference\_value(env, sampleInfo\-\>tsRef, &callFunc);
      napi\_call\_function(env, nullptr, callFunc, 1, &buffer, nullptr);
    }
    
    _//_ _开始录屏_
    static napi\_value ScreenCapture(napi\_env env, napi\_callback\_info info) {
     _ // 获取ArkTS侧传递的回调函数_
      size\_t argc \= 1;
      napi\_value args\[1\] \= {nullptr};
      napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
    
     _ // 录屏相关配置_
      OH\_AVScreenCapture \*capture \= OH\_AVScreenCapture\_Create();
     _ // 设置回调。_
      OH\_AVScreenCapture\_SetErrorCallback(capture, OnError, nullptr);
      OH\_AVScreenCapture\_SetStateCallback(capture, OnStateChange, nullptr);
      _// 回调音视频原始码流_
      OH\_AVScreenCapture\_SetDataCallback(capture, OnBufferAvailable, nullptr);
      _// OH\_AVScreenRecorderConfig__配置录屏视频宽高，音频通道数等_
      OH\_AudioCaptureInfo micCapInfo \= {.audioSampleRate \= 16000, .audioChannels \= 2, .audioSource \= OH\_MIC};
      OH\_VideoCaptureInfo videoCapInfo \= {
        .videoFrameWidth \= 768, .videoFrameHeight \= 1280, .videoSource \= OH\_VIDEO\_SOURCE\_SURFACE\_RGBA};
      OH\_AudioInfo audioInfo \= {
        .micCapInfo \= micCapInfo,
      };
      OH\_VideoInfo videoInfo \= {.videoCapInfo \= videoCapInfo};
      OH\_AVScreenCaptureConfig config \= {.dataType \= OH\_ORIGINAL\_STREAM, .audioInfo \= audioInfo, .videoInfo \= videoInfo};
      OH\_AVScreenCapture\_Init(capture, config);
    
      _// 编码器相关配置_
      SampleInfo sampleInfo;
      sampleInfo.videoCodecMime \= "video/avc";
      sampleInfo.videoWidth \= 768;
      sampleInfo.videoHeight \= 1280;
      sampleInfo.frameRate \= 30;
      sampleInfo.bitrate \= 10000000;
      sampleInfo.audioCodecMime \= OH\_AVCODEC\_MIMETYPE\_AUDIO\_AAC;
      sampleInfo.audioSampleForamt \= OH\_BitsPerSample::SAMPLE\_S16LE;
      sampleInfo.audioSampleRate \= 48000;
      sampleInfo.audioChannelCount \= 2;
      sampleInfo.audioBitRate \= 96000;
      sampleInfo.audioChannelLayout \= OH\_AudioChannelLayout::CH\_LAYOUT\_STEREO;
      sampleInfo.audioMaxInputSize \= sampleInfo.audioSampleRate \* 0.02 \* sampleInfo.audioChannelCount \* sizeof(short);
      sampleInfo.pixelFormat \= AV\_PIXEL\_FORMAT\_RGBA;
    
      _// 保存ArkTS传递的回调函数，在合适时机调用_
      napi\_create\_reference(env, args\[0\], 1, &sampleInfo.tsRef);
      _// 创建线程安全函数_
      napi\_value workName \= nullptr;
      napi\_create\_string\_utf8(env, "ScreenCaptureThreadSafeFunc", NAPI\_AUTO\_LENGTH, &workName);
      napi\_threadsafe\_function tsfn;
      napi\_create\_threadsafe\_function(env, args\[0\], nullptr, workName, 0, 1, nullptr, nullptr, nullptr, callJS, &tsfn);
      sampleInfo.tsfn \= tsfn;
    
     _ // init编码器相关配置_
      Recorder::GetInstance().Init(sampleInfo);
     _ // 指定surface开始录屏。_
      int32\_t retStart \= OH\_AVScreenCapture\_StartScreenCaptureWithSurface(capture, sampleInfo.window);
    
      if (retStart != AV\_ERR\_OK) {
        return nullptr;
      }
      Recorder::GetInstance().Start();
     _ // 开始录屏。_
      OH\_AVScreenCapture\_SetMicrophoneEnabled(capture, true);
      return nullptr;
    }
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports) {
      napi\_property\_descriptor desc\[\] \= {{"screenCapture", nullptr, ScreenCapture, nullptr, nullptr, nullptr, napi\_default, nullptr}};
    napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
    return exports;
    }
    EXTERN\_C\_END
    
    static napi\_module demoModule \= {
      .nm\_version \= 1,
      .nm\_flags \= 0,
      .nm\_filename \= nullptr,
      .nm\_register\_func \= Init,
      .nm\_modname \= "entry",
      .nm\_priv \= ((void \*)0),
      .reserved \= {0},
    };
    
    extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void) { napi\_module\_register(&demoModule); }
    
2.  OnNewOutputBuffer调用线程安全函数，避免死锁。
    
    _/\*_
     _\* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved._
     _\*/_
    
    #include "sample\_callback.h"
    #include "av\_codec\_sample\_log.h"
    
    namespace {
      constexpr int LIMIT\_LOGD\_FREQUENCY \= 50;
      constexpr int32\_t BYTES\_PER\_SAMPLE\_2 \= 2;
    } _// namespace_
    
    _//_ _自定义写入数据函数_
    int32\_t SampleCallback::OnRenderWriteData(OH\_AudioRenderer \*renderer, void \*userData, void \*buffer, int32\_t length)
    {
      (void)renderer;
      (void)length;
      CodecUserData \*codecUserData \= static\_cast<CodecUserData \*\>(userData);
    
     _ // 将待播放的数据，按length长度写入buffer_
      uint8\_t \*dest \= (uint8\_t \*)buffer;
      size\_t index \= 0;
      std::unique\_lock<std::mutex\> lock(codecUserData\-\>outputMutex);
      _// 从队列中取出需要播放的长度为length的数据_
      while (!codecUserData\-\>renderQueue.empty() && index < length) {
        dest\[index++\] \= codecUserData\-\>renderQueue.front();
        codecUserData\-\>renderQueue.pop();
      }
      AVCODEC\_SAMPLE\_LOGD("render BufferLength:%{public}d Out buffer count: %{public}u, renderQueue.size: %{public}u "
      "renderReadSize: %{public}u",
      length, codecUserData\-\>outputFrameCount, codecUserData\-\>renderQueue.size(), index);
    
      codecUserData\-\>frameWrittenForSpeed +=
      length / codecUserData\-\>speed / codecUserData\-\>sampleInfo\-\>audioChannelCount / BYTES\_PER\_SAMPLE\_2;
      codecUserData\-\>currentPosAudioBufferPts \=
        codecUserData\-\>endPosAudioBufferPts \- codecUserData\-\>renderQueue.size() /
          codecUserData\-\>sampleInfo\-\>audioSampleRate /
          codecUserData\-\>sampleInfo\-\>audioChannelCount / BYTES\_PER\_SAMPLE\_2;
    
      if (codecUserData\-\>renderQueue.size() < length) {
        codecUserData\-\>renderCond.notify\_all();
      }
      return 0;
    }
    _//_ _自定义音频流事件函数_
    int32\_t SampleCallback::OnRenderStreamEvent(OH\_AudioRenderer \*renderer, void \*userData, OH\_AudioStream\_Event event)
    {
      (void)renderer;
      (void)userData;
      (void)event;
     _ // 根据event表示的音频流事件信息，更新播放器状态和界面_
      return 0;
    }
    _//_ _自定义音频中断事件函数_
    int32\_t SampleCallback::OnRenderInterruptEvent(OH\_AudioRenderer \*renderer, void \*userData,
      OH\_AudioInterrupt\_ForceType type, OH\_AudioInterrupt\_Hint hint)
    {
      (void)renderer;
      (void)userData;
      (void)type;
      (void)hint;
      _//_ _根据type和hint表示的音频中断信息，更新播放器状态和界面_
      return 0;
    }
    _//_ _自定义异常回调函数_
    int32\_t SampleCallback::OnRenderError(OH\_AudioRenderer \*renderer, void \*userData, OH\_AudioStream\_Result error)
    {
      (void)renderer;
      (void)userData;
      (void)error;
      AVCODEC\_SAMPLE\_LOGE("OnRenderError");
      _// 根据error表示的音频异常信息，做出相应的处理_
      return 0;
    }
    
    void SampleCallback::OnCodecError(OH\_AVCodec \*codec, int32\_t errorCode, void \*userData)
    {
      (void)codec;
      (void)errorCode;
      (void)userData;
      AVCODEC\_SAMPLE\_LOGE("On codec error, error code: %{public}d", errorCode);
    }
    
    void SampleCallback::OnCodecFormatChange(OH\_AVCodec \*codec, OH\_AVFormat \*format, void \*userData)
    {
      AVCODEC\_SAMPLE\_LOGI("On codec format change");
    }
    
    void SampleCallback::OnNeedInputBuffer(OH\_AVCodec \*codec, uint32\_t index, OH\_AVBuffer \*buffer, void \*userData)
    {
      if (userData \== nullptr) {
        return;
      }
      (void)codec;
      CodecUserData \*codecUserData \= static\_cast<CodecUserData \*\>(userData);
      std::unique\_lock<std::mutex\> lock(codecUserData\-\>inputMutex);
      if (codecUserData\-\>isEncFirstFrame) {
        OH\_AVFormat \*format \= OH\_VideoEncoder\_GetInputDescription(codec);
        OH\_AVFormat\_GetIntValue(format, OH\_MD\_KEY\_VIDEO\_PIC\_WIDTH, &codecUserData\-\>width);
        OH\_AVFormat\_GetIntValue(format, OH\_MD\_KEY\_VIDEO\_PIC\_HEIGHT, &codecUserData\-\>height);
        OH\_AVFormat\_GetIntValue(format, OH\_MD\_KEY\_VIDEO\_STRIDE, &codecUserData\-\>widthStride);
        OH\_AVFormat\_GetIntValue(format, OH\_MD\_KEY\_VIDEO\_SLICE\_HEIGHT, &codecUserData\-\>heightStride);
        OH\_AVFormat\_Destroy(format);
        codecUserData\-\>isEncFirstFrame \= false;
      }
      codecUserData\-\>inputBufferInfoQueue.emplace(index, buffer);
      codecUserData\-\>inputCond.notify\_all();
    }
    
    void SampleCallback::OnNewOutputBuffer(OH\_AVCodec \*codec, uint32\_t index, OH\_AVBuffer \*buffer, void \*userData)
    {
      if (userData \== nullptr) {
        return;
      }
      (void)codec;
      CodecUserData \*codecUserData \= static\_cast<CodecUserData \*\>(userData);
      std::unique\_lock<std::mutex\> lock(codecUserData\-\>outputMutex);
      codecUserData\-\>outputBufferInfoQueue.emplace(index, buffer);
    
     _ // 回调arkts_
      SampleInfo \*sampleInfo \= codecUserData\-\>sampleInfo;
      if (sampleInfo) {
        uint8\_t \*buf \= OH\_AVBuffer\_GetAddr(buffer);
        if (buf) {
          sampleInfo\-\>buf \= buf;
         _ // 调用线程安全函数_
          napi\_acquire\_threadsafe\_function(sampleInfo\-\>tsfn);
          napi\_call\_threadsafe\_function(sampleInfo\-\>tsfn, (void \*)sampleInfo, napi\_tsfn\_blocking);
        }
      }
      codecUserData\-\>outputCond.notify\_all();
      AVCODEC\_SAMPLE\_LOGI("output buffer");
    }
    
3.  导出Native方法提供ArkTS使用。
    
    export const screenCapture: (cb:(buffer: ArrayBuffer)\=\>void) \=\> void;
    
4.  ArkTS侧调用录屏方法。
    
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import testNapi from 'libentry.so';
    
    @Entry
    @Component
    struct Index {
      @State message: string \= 'Hello World';
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() \=\> {
                this.message \= 'Welcome';
                testNapi.screenCapture((buffer: ArrayBuffer) \=\> {
                  hilog.info(100, 'capture buffer ===', buffer.byteLength.toString());
                });
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }