通过阅读本文,您可以了解 Android 输出音视频流裸数据的方法。
输出视频数据
1. 开启视频裸数据回调,调用以下接口:
public int enableVideoSampleObserver(boolean enable, int position);
说明
enable : true 表示订阅视频裸数据;false 表示关闭订阅视频裸数据。
position:表示获取的视频数据所处的位置
public static class DingRtcVideoObservePosition { /** 采集视频数据,对应输出回调 onCaptureVideoFrame。 */ public static final int DingRtcPositionPostCapture = 1; /** 渲染视频数据,对应输出回调 onRemoteVideoFrame。 */ public static final int DingRtcPositionPreRender = 1 << 1; /** 编码前视频数据,对应输出回调 OnPreEncodeVideoFrame。 */ public static final int DingRtcPositionPreEncoder = 1 << 2; }
调用此接口后,即可开始订阅视频裸数据,裸数据通过回调接口返回。
2. 注册监听获取视频裸数据回调,方法如下:
public void registerVideoSampleObserver(DingRtcVideoObserver observer);
3. 通过DingRtcVideoObserver回调获取视频裸数据,方法如下:
/**
* @brief 获取视频数据输出格式。
* @return 期望视频输出格式。
* @return 视频数据输出格式。
*/
public DingRtcVideoFormat onGetVideoFormatPreference() {
return DingRtcVideoFormat.DingRtcVideoFormatI420;
}
/**
* @brief 订阅的本地采集视频数据回调。
* @param sourceType 视频流类型。
* @param videoSample 视频裸数据。
* @return
* - true: 需要写回SDK(默认写回,需要操作DingRtcVideoSample.data时必须要写回);
* - false: 不需要写回SDK(需要直接操作DingRtcVideoSample.dataFrameY、DingRtcVideoSample.dataFrameU、DingRtcVideoSample.dataFrameV时使用)。
*/
public boolean onLocalVideoSample(DingRtcEngine.DingRtcVideoSourceType sourceType, DingRtcEngine.DingRtcVideoSample videoSample) {
return false;
}
/**
* @brief 订阅的远端视频数据回调。
* @param callId 用户ID。
* @param sourceType 视频流类型。
* @param videoSample 视频裸数据。
* @return
* - true:需要写回SDK(默认写回,需要操作DingRtcVideoSample.data时必须要写回);
* - false:不需要写回SDK(需要直接操作DingRtcVideoSample.dataFrameY、DingRtcVideoSample.dataFrameU、DingRtcVideoSample.dataFrameV时使用)。
*/
public boolean onRemoteVideoSample(String callId, DingRtcEngine.DingRtcVideoSourceType sourceType, DingRtcEngine.DingRtcVideoSample videoSample) {
return false;
}
/**
* @brief 本地编码前视频数据。
* @param sourceType 视频流类型。
* @param videoSample 视频裸数据。
* @return
* - true:需要写回SDK(默认写回,需要操作DingRtcVideoSample.data时必须要写回);
* - false:不需要写回SDK(需要直接操作DingRtcVideoSample.dataFrameY、DingRtcVideoSample.dataFrameU、DingRtcVideoSample.dataFrameV时使用)。
*/
public boolean onPreEncodeVideoFrame(DingRtcEngine.DingRtcVideoSourceType sourceType, DingRtcEngine.DingRtcVideoSample videoSample) {
return false;
}
说明
调用 enableVideoSampleObserver(true)以及registerVideoSampleObserver后:
通过下面回调告诉引擎需要获取的视频格式:
onGetVideoFormatPreference
通过以下三个回调获取对应的视频裸数据:
onLocalVideoSample为本地预览数据回调,在开始预览之后可收到数据流。
onPreEncodeVideoFrame为本地编码前数据回调,在推流之后可收到数据流。
onRemoteVideoSample为拉流数据回调,subscribe 拉流成功后可收到数据流。
4. 订阅的远端视频裸数据写入本地YUV文件示例代码:
// 开启视频数据dump
private int enableDumpVideo() {
String pcmPath = "/sdcard/dump_i420.pcm";
File yuvFile = new File(pcmPath);
try {
mYuvBos = new BufferedOutputStream(new FileOutputStream(yuvFile, true));
} catch (FileNotFoundException e) {
Log.e(TAG, "open yuv file failed" + e.getMessage());
return -1;
}
mRtcEngine.enableVideoSampleObserver(true, DingRtcPositionPostCapture);
mRtcEngine.registerVideoSampleObserver(new DingRtcEngine.DingRtcVideoObserver() {
@Override
public DingRtcEngine.DingRtcVideoFormat onGetVideoFormatPreference() {
return DingRtcEngine.DingRtcVideoFormat.DingRtcVideoFormatI420;
}
@Override
public boolean onLocalVideoSample(DingRtcEngine.DingRtcVideoSourceType sourceType, DingRtcEngine.DingRtcVideoSample videoSample) {
writeToDumpFile(videoSample);
return false;
}
});
return 0;
}
// 关闭视频数据dump
private int disableDumpVideo() {
mRtcEngine.enableVideoSampleObserver(false, DingRtcPositionPostCapture);
mRtcEngine.registerVideoSampleObserver(null);
try {
if (mYuvBos != null) {
mYuvBos.close();
mYuvBos = null;
}
} catch (IOException e) {
Log.e(TAG, "close output stream error. " + e.getMessage());
return -1;
}
return 0;
}
private void writeToDumpFile(DingRtcEngine.DingRtcVideoSample videoSample) {
try {
int uvWidth = (videoSample.width + 1) / 2;
int uvHeight = (videoSample.height + 1) / 2;
// Write Y plane
for (int i = 1; i < videoSample.height; i++) {
mYuvBos.write(videoSample.data, i * videoSample.strideY, videoSample.width);
}
// Write U plane
for (int i = 1; i < uvHeight; i++) {
mYuvBos.write(videoSample.data, videoSample.height * videoSample.strideY + i * videoSample.strideU, uvWidth);
}
// Write V plane
for (int i = 1; i < uvHeight; i++) {
mYuvBos.write(videoSample.data, videoSample.height * videoSample.strideY + uvHeight * videoSample.strideU + i * videoSample.strideV, uvWidth);
}
mYuvBos.flush();
} catch (IOException e) {
Log.e(TAG, "ioexception while write. " + e.getMessage());
}
}
输出音频裸数据
1. 启动音频裸数据的回调监听:
public int enableAudioFrameObserver(boolean enable, int position, DingRtcAudioFrameObserverConfig config);
说明
enable : true 表示开启;false表示关闭。
position:表示获取的音频数据所处的位置
public static class DintRtcAudioObservePosition { /** 采集的音频数据,对应输出回调 OnCapturedAudioFrame。 */ public static final int RtcEngineAudioPositionCaptured = 1; /** 3A后的音频数据,对应输出回调 OnProcessCapturedAudioFrame。 */ public static final int RtcEngineAudioPositionProcessCaptured = 1 << 1; /** 推流的音频数据,对应输出回调 OnPublishAudioFrame。 */ public static final int RtcEngineAudioPositionPub = 1 << 2; /** 播放的音频数据,对应输出回调 OnPlaybackAudioFrame。 */ public static final int RtcEngineAudioPositionPlayback = 1 << 3; /** 拉流的远端音频数据,对应输出回调 OnRemoteUserAudioFrame。 */ public static final int RtcEngineAudioPositionRemoteUser = 1 << 4; }
config:表示获取的音频裸数据的规格
public static class DingRtcAudioFrameObserverConfig { /** 回调音频采样率。 */ public DingRtcAudioSampleRate sampleRate = DingRtcAudioSampleRate.DingRtcAudioSampleRate_48000; /** 回调音频声道数。 */ public DingRtcAudioNumChannel channels = DingRtcAudioNumChannel.DingRtcMonoAudio; /** 回调音频读写权限,只读取音频裸数据(仅用于获取音频裸数据场景) 或者 读取并且写入音频裸数据(用于修改音频数据内容场景)。 */ public DingRtcAudioFramePermission permission = DingRtcAudioFramePermission.DingRtcAudioFramePermissionReadOnly; /** 用户自定义参数。 */ public int userDefinedInfo = DingRtcAudioFrameObserverUserDefinedInfoBitMask.DingRtcAudioFrameObserverUserDefinedInfoBitMaskMixExRender.getValue(); }
调用此接口后,即可订阅音频裸数据或取消订阅音频裸数据。
2. 注册监听音频裸数据的回调:
public void registerAudioFrameObserver(DingRtcAudioFrameObserver observer);
3. 通过DingRtcAudioFrameObserver回调获取音频裸数据,方法如下:
/**
* @brief 远端拉流数据回调
* @param uid 远端用户ID。
* @param frame 音频数据。
* @note 默认关闭
* - 该接口支持设置采样率、声道数。
* - 该接口支持读写模式。
* - 请不要在此回调函数中做任何耗时操作,否则可能导致声音异常。
*/
void onRemoteUserAudioFrame(String uid, DingRtcAudioFrame frame);
/**
* @brief 采集裸数据回调。
* @param frame 音频数据。
* @note 默认关闭。
* - 该接口支持设置采样率、声道数。
* - 该接口支持读写模式。
* - 请不要在此回调函数中做任何耗时操作,否则可能导致声音异常。
*/
void onCapturedAudioFrame(DingRtcAudioFrame frame);
/**
* @since 3.0
* @param frame 音频数据。
* @brief 3A后数据回调。
* @note 默认关闭。
* - 该接口支持设置采样率、声道数。
* - 该接口支持读写模式。
* - 请不要在此回调函数中做任何耗时操作,否则可能导致声音异常。
*/
void onProcessCapturedAudioFrame(DingRtcAudioFrame frame);
/**
* @since 3.0
* @param frame 音频数据。
* @brief 推流数据回调。
* @note 默认关闭。
* - 该接口支持设置采样率、声道数。
* - 该接口只支持只读模式。
* - 请不要在此回调函数中做任何耗时操作,否则可能导致声音异常。
*/
void onPublishAudioFrame(DingRtcAudioFrame frame);
/**
* @since 3.0
* @param frame 音频数据。
* @brief 播放数据回调。
* @note 默认关闭开启。
* - 该接口支持设置采样率、声道数。
* - 该接口支持读写模式。
* - 请不要在此回调函数中做任何耗时操作,否则可能导致声音异常。
*/
void onPlaybackAudioFrame(DingRtcAudioFrame frame);
4. 订阅远端音频裸数据,并写入本地 pcm 文件示例代码:
//开启音频数据dump
private int enableDumpAudio() {
String pcmPath = "/sdcard/dump_i420.pcm";
File yuvFile = new File(pcmPath);
try {
mPcmBos = new BufferedOutputStream(new FileOutputStream(yuvFile, true));
} catch (FileNotFoundException e) {
Log.e(TAG, "open yuv file failed" + e.getMessage());
return -1;
}
mRtcEngine.enableAudioFrameObserver(true, RtcEngineAudioPositionCaptured);
mRtcEngine.registerAudioFrameObserver(new DingRtcEngine.DingRtcAudioFrameObserver() {
@Override
public void onCapturedAudioFrame(DingRtcEngine.DingRtcAudioFrame frame) {
writeToPcmFile(frame);
}
@Override
public void onProcessCapturedAudioFrame(DingRtcEngine.DingRtcAudioFrame frame) {
}
@Override
public void onPublishAudioFrame(DingRtcEngine.DingRtcAudioFrame frame) {
}
@Override
public void onPlaybackAudioFrame(DingRtcEngine.DingRtcAudioFrame frame) {
}
});
return 0;
}
// 关闭音频数据dump
private int disableDumpAudio() {
mRtcEngine.enableAudioFrameObserver(false, RtcEngineAudioPositionCaptured);
mRtcEngine.registerAudioFrameObserver(null);
try {
if (mPcmBos != null) {
mPcmBos.close();
mPcmBos = null;
}
} catch (IOException e) {
Log.e(TAG, "close output stream error. " + e.getMessage());
return -1;
}
return 0;
}
private int writeToPcmFile(DingRtcEngine.DingRtcAudioFrame audioFrame) {
int totalBytes = audioFrame.numChannels * audioFrame.numSamples * audioFrame.bytesPerSample;
byte[] buffer = new byte[totalBytes];
audioFrame.data.get(buffer);
try {
if(mPcmBos!= null) {
mPcmBos.write(buffer, 0, totalBytes);
mPcmBos.flush();
}
} catch (IOException e) {
Log.e(TAG, "close output stream error. " + e.getMessage());
return -1;
}
return 0;
}
该文章对您有帮助吗?