You need to enable JavaScript to run this app.
文档中心
云手机

云手机

复制全文
下载 pdf
音视频注入
音视频裸数据注入(Pod 注入)
复制全文
下载 pdf
音视频裸数据注入(Pod 注入)
火山引擎云手机支持虚拟摄像头、虚拟麦克风功能,能够实现将音视频裸数据注入到云机实例的虚拟摄像头/麦克风中,供实例上运行的应用获取和使用。​
本文介绍如何使用火山引擎云手机虚拟摄像头 & 麦克风 SDK(Proxy SDK),将获取到的音视频数据注入云机实例。内部采集音视频注入功能请参考内部采集音视频注入;外部音视频数据注入功能请参考外部音视频源注入(客户端注入)。​
适用场景​
使用 Proxy SDK 注入音视频适用的场景如下:​
  • 视频文件合流注入 用户在后台运行服务,自行解码多个视频文件并将其合为一路视频流后,使用 Proxy SDK 将该路视频流注入到虚拟摄像头中。​
  • 网络视频流注入 用户从网络源拉取 RTMP 等格式的在线视频流并完成解码,使用 Proxy SDK 将解码后的音视频数据注入虚拟摄像头和麦克风。​
  • Unity 渲染画面注入 用户在后台运行 Unity 渲染引擎,将渲染完成的画面及其生成的音频通过 Proxy SDK 注入到虚拟摄像头和麦克风。​
  • 自定义注入方案 Proxy SDK 支持用户根据特定需求,开发自定义的音视频注入场景。​
前提条件​
  • 具备旗舰型配置的实例资源。高负载场景请使用旗舰型配置的设备,避免因性能不足造成的卡顿、丢帧、杂音、黑屏等问题。​
  • 需要音视频注入的应用程序已在控制台完成应用加签(适用于自主开发的应用,第三方应用无需关注)。​
注意事项​
  • 视频裸数据注入只支持竖屏模式下使用,横屏模式下会出现显示异常等不兼容情况。​
  • Proxy SDK 仅接收 PCM 格式的音频帧,和 YUV-I420(默认)或 RGBA 格式的视频帧数据。非上述格式数据,在注入前请先进行解码转换,具体参考 Demo。​
  • Proxy SDK 内部已实现音视频自动同步逻辑,仅需保证传入的音频帧与视频帧相匹配。​
  • 三种音视频注入模式存在优先级和切换限制:​
  • 本功能与客户端内部源音视频注入、客户端外部源音视频注入(Pod 注入)功能互斥,即云机实例同一时间只能接收一种注入源;​
  • 当三种模式并存时,云机实例接收注入源的优先级为:客户端外部源音视频注入>音视频裸数据注入(Pod 注入)>客户端内部源音视频注入;​
  • 若切换至其他注入源,请务必先关闭摄像头和麦克风,关闭当前正在注入的应用程序,否则会导致切换失败。​
功能实现​
​Image​​
下载 Proxy SDK​
下载 SDK 并拷贝至你的项目。​
ProxySDK​
​
语言​
SDK 文件​
Java​
​
​
proxysdk-0.2.2.3.2.aar.zip
​
​
C++​
armeabi-v7a:​
​
​
libvdevice_armeabi-v7a.so
​
​
​
arm64-v8a:​
​
​
libvdevice_arm64-v8a.so
​
​
​
Unity 插件​
专为 Unity 场景开发的用于采集 Unity Texture 的插件。需配合 Proxy SDK 一起使用。​
​
​
injectplugin-release.aar.zip
​
​
集成 Proxy SDK​
Proxy Java SDK​
  1. 将解压后的 SDK 文件夹复制到项目的 libs 目录中。​
  1. 配置项目依赖。打开项目的 build.gradle 文件,在 dependencies 代码块中添加以下配置:​
  • ​
    dependencies {​
    implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])​
    }​
    ​
  1. 同步项目。保存build.gradle文件的更改后,执行 gradle sync 即可。​
Proxy C++ SDK​
  1. 配置 CMakeLists.txt 以引入 SDK 库和头文件。​
  • ​
    # 添加 SDK 的 so 库​
    add_library(vdevice SHARED IMPORTED)​
    set_target_properties(vdevice PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libvdevice.so)​
    # 指定头文件路径​
    include_directories(src/main/cpp/include)​
    ​
  1. 在 CMakeLists.txt 中链接 NDK 日志库。​
  • ​
    target_link_libraries( # Specifies the target library.​
    native_test​
    # Links the target library to the log library​
    # included in the NDK.​
    vdevice​
    ${log-lib} )​
    ​
  1. 在目标库中使用 SDK。​
  • ​
    using namespace android;​
    ​
    static void onAudioRequest(int start) {​
    LOGI("onAudioRequest: %d", start);​
    }​
    static void onCameraRequest(int start, int cameraId) {​
    LOGI("onCameraRequest start = %d, cameraId = %d", start, cameraId);​
    }​
    ​
    bool testRegisterAudioCblk() {​
    LOGI("call testRegisterAudioCblk");​
    static AudioProxy *audioProxy = AudioProxy::getInstance();​
    return audioProxy->registerCallback(onAudioRequest);​
    }​
    ​
    bool testRegisterCameraCblk() {​
    LOGI("call testRegisterAudioCblk");​
    static CameraProxy *cameraProxy = CameraProxy::getInstance();​
    return cameraProxy->registerCallback(onCameraRequest);​
    }​
    ​
示例代码​
使用 Proxy SDK 实现音视频裸数据注入功能时,可能会使用到以下三方库,请自行引入:​
  • Ffmpeg:用于音视频格式处理​
  • libyuv:用于 YUV 图像处理,如裁剪、旋转、格式转换​
基础功能示例 Demo​
Proxy Java SDK​
音频注入​
注意​
使用 Proxy SDK 注入音频前,如正在使用云手机 Android SDK 注入外部采集音频,请先调用 setAudioInjectionState(false) 接口停止从客户端注入音频,否则会导致注入失败。​
方式一:从指定文件注入​
​
// step1. 获取DeviceProxyContext​
DeviceProxyContext deviceProxyContext = DeviceProxyContext.getInstance(context);​
// step2. 获取AudioProxyManager​
AudioProxyManager proxy = deviceProxyContext.getAudioProxyManager();​
// step3. 设置注入参数(采样率、声道数、采样深度,采样周期。默认 48kHz, 双声道、16 位采样深度)​
proxy.setAudioSampleConfigs(new AudioSampleConfig(48000, 2, 2, 10));​
// step4. 指定音频文件路径和注入类型​
String path = "/vendor/res/sound/48000-16-2.pcm";​
proxy.recordByFile(path, FileRecordType.TYPE_CIRCLE);​
​
方式二:自定义 AudioCallback 注入​
​
// step1. 获取DeviceProxyContext​
DeviceProxyContext deviceProxyContext = DeviceProxyContext.getInstance(context);​
// step2. 获取AudioProxyManager​
AudioProxyManager proxy = deviceProxyContext.getAudioProxyManager();​
// step3(可选). 设置注入参数(采样率、声道数、采样深度,默认48kHz, 双声道、16位采样深度)​
proxy.setAudioSampleConfigs(new AudioSampleConfig(48000, 2, 2));​
​
//设置每帧时长或者设置每帧 buffer 大小​
proxy.setSamplePeriodInMs(10);​
//proxy.setAudioBufferSize(size);​
​
AudioTask task = new AudioTask(proxy);​
​
AudioCallback callback = new AudioProxyManager.AudioCallback() {​
@Override​
public void onStart() {​
// 开始注入音频数据​
task.startAudioRecord();​
}​
​
@Override​
public void onResume() {}​
​
@Override​
public void onPause() {}​
​
@Override​
public void onStop() {​
// 停止注入音频数据​
task.stopAudioRecord();​
}​
​
@Override​
public void onRequest() {}​
};​
// step4. 注册AudioCallback,监听系统打开/关闭Mic​
proxy.registerCallback(callback);​
​
class AudioTask {​
private AudioProxyManager mProxy;​
​
public AudioTask(AudioProxyManager proxy) {​
mProxy = proxy;​
}​
​
public void startAudioRecord() {​
...​
// step5. 调用putAudioFrame注入音频帧​
mProxy.putAudioFrame(buffer);​
...​
}​
​
public void stopAudioRecord() {​
// step6. 结束注入音频帧​
...​
}​
}​
​
方式三:录制指定应用程序音频作为注入源​
​
// step1. 获取 DeviceProxyContext​
DeviceProxyContext deviceProxyContext = DeviceProxyContext.getInstance(context);​
// step2. 获取 AudioProxyManager​
AudioProxyManager manager = deviceProxyContext.getAudioProxyManager();​
// step3. 指定 App 包名,确定音频源。若注入源为自主开发应用,请保证音频格式为 AudioAttributes.USAGE_MEDIA 或AudioAttributes.USAGE_GAME,否则无法录入​
manager.registerAppAsAudioSource("com.dimowner.audiorecorder");​
​
视频注入​
注意​
  • 使用 Proxy SDK 注入视频前,如正在使用云手机 Android SDK 注入外部采集视频,请先调用 setVideoInjectionState(false) 接口停止从客户端注入视频,否则会导致注入失败。​
  • 调用 getCameraProxyManager 后,程序应暂停 200 毫秒(ms),以确保 CameraProxyManager 实例已正确初始化,然后再注册 CameraCallback 回调。​
  1. 获取 ProxyContext。​
  1. 获取 CameraProxyManager。​
  1. 设置 CameraCallback,监听 onStart() 和 onStop() 接口。​
  1. 调用 putVideoFrame 注入视频帧。​
示例代码参考:​
​
public class CameraDemoActivity extends AppCompatActivity {​
private DeviceProxyContext proxyContext;​
private CameraProxyManager cameraProxyManager;​
private static final int WIDTH = 1280; // Frame width​
private static final int HEIGHT = 720; // Frame height​
private static final int FORMAT = VideoFrameFormat.I420; // 帧格式​
private VideoFrameConfig videoFrameConfig = new VideoFrameConfig(WIDTH, HEIGHT, FORMAT, 0, 0);​
private byte[] demoBuffer;​
​
private CameraProxyManager.CameraCallback cameraCallback = new CameraProxyManager.CameraCallback() {​
@Override​
public void onStart(int id) {​
handler.post(renderThread);​
}​
​
@Override​
public void onResume(int id) {​
}​
​
@Override​
public void onPause(int id) {​
}​
​
@Override​
public void onStop(int id) {​
handler.removeCallbacks(renderThread);​
}​
​
@Override​
public void onRequest(int id) {​
}​
};​
private Handler handler;​
private Runnable renderThread = new Runnable() {​
@Override​
public void run() {​
if(cameraProxyManager != null) {​
Arrays.fill(demoBuffer, (byte) 0x00); //TODO fill image data​
cameraProxyManager.putVideoFrame(demoBuffer, demoBuffer.length, videoFrameConfig);​
handler.postDelayed(this, 33); // 30fps​
}​
}​
};​
@Override​
protected void onCreate(Bundle savedInstanceState) {​
super.onCreate(savedInstanceState);​
setContentView(R.layout.activity_camera_demo);​
handler = new Handler(Looper.myLooper());​
proxyContext = DeviceProxyContext.getInstance(this);​
try {​
Thread.sleep(200);​
} catch (InterruptedException e) {​
throw new RuntimeException(e);​
}​
if(proxyContext != null){​
cameraProxyManager = proxyContext.getCameraProxyManager();​
if(cameraProxyManager != null){​
cameraProxyManager.unregisterCallback(cameraCallback);​
boolean ret = cameraProxyManager.registerCallback(cameraCallback);​
demoBuffer = new byte[videoFrameConfig.width * videoFrameConfig.height * 3 / 2]; // I420 buffer​
}​
}​
}​
}​
​
Proxy C++ SDK​
​
​
C++_demo.cpp
​
​
​
高级功能示例 Demo​
可参考以下示例,实现将解码视频文件后获取到的视频流数据注入至云机实例。​
​
​
VirtCamera.zip
​
​
​
API 参考​
Proxy Java SDK​
CameraProxyManager​
​
public interface CameraProxyManager extends ProxyInterface {​
/**​
* 注册Camera打开/关闭事件回调监听接口,注册成功返回true,​
* 如果返回false,可能是优先级低,或者正在使用 ,或者重复注册​
* @param cameraCallback 回调​
* @return true 成功,false 失败​
*/​
boolean registerCallback(CameraCallback cameraCallback);​
​
/**​
* 取消Camera打开/关闭事件回调接口,取消成功返回true,​
* 如果返回false,可能是底层服务异常,或者binder服务连接异常​
* @param cameraCallback 回调,需要和register保持一致​
* @return true 成功,false 失败​
*/​
boolean unregisterCallback(CameraCallback cameraCallback);​
​
/**​
* 向虚拟摄像头注入一帧画面,如果成功返回true,​
* 如果返回false,可能是CameraCallback没有注入,就调用了put方法,也可能是camera没有打开,或者camera服务异常。​
* @param buffer 图像数据的ByteBuffer对象​
* @param config 图像数据的大小,旋转方向等。​
* @return true 成功,false 失败​
*/​
boolean putVideoFrame(@NonNull ByteBuffer buffer, @NonNull VideoFrameConfig config);​
​
/**​
* 向虚拟摄像头注入一帧画面,如果成功返回true,​
* 如果返回false,可能是CameraCallback没有注入,就调用了put方法,也可能是camera没有打开,或者camera服务异常。​
* @param buffer 图像数据的byte array对象,不建议使用,大概率触发频繁GC动作​
* @param config 图像数据的大小,旋转方向等。​
* @return true 成功,false 失败​
*/​
boolean putVideoFrame(@NonNull byte[] buffer, int size, @NonNull VideoFrameConfig config);​
​
@Deprecated​
void setPreviewFile(String filepath, VideoFrameConfig config);​
​
interface CameraCallback {​
/**​
* 三方应用调用系统Camera API打开Camera时候回调​
* @param id camera id,0:Back后置,1:Front前置​
*/​
void onStart(int id);​
​
/**​
* 暂不使用,忽略​
* @param id​
*/​
void onResume(int id);​
​
/**​
* 暂不使用,忽略​
* @param id​
*/​
void onPause(int id);​
/**​
* 三方应用调用系统Camera API关闭Camera时候回调​
* @param id camera id,0:Back后置,1:Front前置​
*/​
void onStop(int id);​
​
/**​
* 暂不使用,忽略​
* @param id​
*/​
void onRequest(int id);​
}​
}​
​
VideoFrameConfig​
​
public class VideoFrameConfig implements Parcelable {​
public int width; // Frame width​
public int height; // Frame height​
public int format; // Frame format,current only support VideoFrameFormat.I420​
public int rotation; // Frame rotation​
public int cameraId; // Frame source, default 0​
}​
​
VideoFrameFormat​
​
public class VideoFrameFormat implements Parcelable {​
public static final int RGBA_8888 = PixelFormat.RGBA_8888;​
public static final int RGBX_8888 = PixelFormat.RGBX_8888;​
public static final int I420 = ImageFormat.YUV_420_888; //当前只支持这个格式​
public static final int NV21 = ImageFormat.NV21;​
public static final int JPEG = ImageFormat.JPEG;​
public static final int RGB_888 = ImageFormat.FLEX_RGB_888;​
}​
​
Proxy C++ SDK​
Audio 相关接口​
AudioProxy.h头文件如下:​
​
​
AudioProxy.h
​
​
​
// 每一帧pcm数据大小(目前支持48000Hz, 双声道、16位采样深度pcm音频数据)​
static const int FRAME_SIZE = 48000 * 2 * 2 /100;​
static AudioProxy *getInstance();​
​
// 监听麦克风打开关闭回调(0-关闭,1-打开)​
typedef void (*Callback)(int);​
bool registerCallback(Callback cb);​
​
// 注入每一帧数据​
size_t pushAudioFrame(const void* data);​
​
Camera 相关接口​
CameraProxy.h头文件如下:​
​
​
CameraProxy.h
​
​
​
// 帧格式,目前只支持YUV I420 格式​
static const int FRAME_FORMAT = 35; ​
static CameraProxy *getInstance();​
​
// 收到camera打开callback后,开始注入前,需要调用​
bool prepare();​
// 收到camera关闭callback后,需要调用​
bool release();​
// 监听Camera打开和关闭的回调,第一个参数代表开/关(0-关闭,1-打开),第二个参数是Camera的ID(0-后置摄像头,1-前置摄像头)​
typedef void (*CameraCallback)(int, int);​
bool registerCallback(CameraCallback cb);​
​
// 注入的每一帧数据(cameraid用回调里面的cameraid)​
size_t pushVideoFrame(const uint8_t* data, int size, int width, int height, int format, int rotation, int cameraId);​
​
​
最近更新时间:2026.02.28 17:22:51
这个页面对您有帮助吗?
有用
有用
无用
无用