多媒体场景开发
音频管理场景开发
本节按本地播放、蓝牙切换、语音通话和 A2DP Sink 播放等场景说明调用流程。模块背景、架构和功能说明见多媒体软件背景知识。
错误码
表 1 音频管理错误码
错误代码 |
宏定义 |
描述 |
|---|---|---|
0 |
AUDIO_SUCCESS |
成功。 |
-1 |
AUDIO_ERROR |
失败。 |
-2 |
AUDIO_INVALID_PARAMS |
无效参数。 |
-3 |
AUDIO_INVALID_OPERATION |
无效操作。 |
-4 |
AUDIO_PERMISSION_DENIED |
权限拒绝。 |
-5 |
AUDIO_NO_INIT |
未初始化。 |
-6 |
AUDIO_NO_MEM |
没有内存。 |
-7 |
AUDIO_RETRY_READ |
再读取一次。 |
-8 |
AUDIO_DEAD_OBJECT |
对象已死。 |
-9 |
AUDIO_BAD_STATE |
异常状态。 |
开发指导
场景接口参考
AudioManager类
音频管理模块负责管理输入输出设备、设备连接状态、音频中断、音量、静音和响铃模式等全局音频策略。应用应先获取并初始化 AudioManager,再创建输入流、输出流或采集器。
接口说明:AudioManager API Reference。
InterruptListener类
中断监听器用于接收音频焦点、中断和恢复事件。应用应在回调中快速更新播放或采集状态,避免执行阻塞业务。
接口说明:OnInterrupt。
AudioStreamIn类
AudioStreamIn 提供基础输入流的初始化、设备选择、启动、缓冲区获取、停止和释放能力,适用于需要直接管理输入 PCM 数据的业务。
接口说明:AudioStream API Reference。
AudioStreamOut类
AudioStreamOut 提供基础输出流的初始化、设备选择、启动、送入 PCM 数据、停止和释放能力,适用于本地播放和业务音频输出。
接口说明:AudioStream API Reference。
数据类型与结构参考
以下类型和结构的完整定义、枚举值及成员说明以 SDK 接口头文件为准。
| 类型或结构 | 用途 |
|---|---|
| AudioSession | 音频会话 ID 数据类型。 |
| DeviceFlag | 音频输入或输出设备标志位。 |
| AudioDeviceType | 音频设备类型。 |
| AudioDeviceInfo | 描述音频设备的类型、名称、地址和可用能力。 |
| AudioDeviceConnectState | 表示音频设备当前的连接状态,用于判断设备是否可用。 |
| AudioRingMode | 选择铃声、振动或静音等来电提示模式。 |
| AudioSourceType | 指定录音数据的来源,例如麦克风或系统音频。 |
| AudioDeviceDesc | 封装音频设备的标识、类型和连接属性。 |
| AudioCodecFormat | 选择音频数据的编码格式,用于匹配编解码器能力。 |
| AudioStreamType | 区分媒体、通话、系统等音频流用途,决定路由和策略。 |
| AudioLinkDirection | 指定音频链路的数据方向,即输入、输出或双向。 |
| AudioStreamFlag | 设置音频流的路由、共享和回调等行为标志。 |
| AudioChannelMode | 选择音频通道布局,例如单声道或立体声。 |
| AudioBitWidth | 指定每个音频采样点的位宽。 |
| AudioOffloadInfo | 描述音频硬件卸载播放所需的格式和缓冲参数。 |
| AudioStrategy | 描述音频策略配置,用于选择设备和处理音频焦点。 |
| enum AudioInterruptType | 表示音频中断事件类型,例如新的焦点请求或释放。 |
| AudioInterruptHintType | 表示收到中断时应用应采取的处理提示。 |
| AudioStreamInfo | 描述音频流的采样率、通道数、位宽和格式。 |
| AudioRendererConfig | 封装音频播放的采样率、通道、格式和缓冲配置。 |
| CapturerInputConfig | 封装音频采集的输入源、采样率、通道和格式配置。 |
| AudioCallbackEventType | 表示播放或采集回调中的事件类型。 |
| RendererCallback | 定义播放器状态、时间和错误事件的回调函数类型。 |
| CapturerCallback | 定义采集器状态、数据和错误事件的回调函数类型。 |
| CapturerBuffer | 描述采集器回调交付的音频缓冲区及其长度。 |
| InterruptDebugInfo | 记录音频中断请求和处理结果,便于定位焦点问题。 |
| DeviceDebugInfo | 记录音频设备选择、连接和路由状态,便于调试。 |
| StreamDebugInfo | 记录音频流的创建、运行和释放状态,便于调试。 |
| AudioInterruptDebugInfo | 记录音频中断的详细原因、提示和处理状态。 |
| AudioStreamDebugInfo | 记录音频流参数、状态和异常信息。 |
| AudioDeviceDebugInfo | 记录音频设备属性、连接状态和路由信息。 |
| AudioServiceDebugInfo | 记录音频服务运行状态及服务侧错误信息。 |
| AudioInterrupt | 封装一次音频焦点请求及其中断处理策略。 |
| AudioManagerDebugInfo | 封装音频管理器的调试统计和运行状态。 |
须知: 开发前请参见“音频配置”章节修改所需的配置。
本地音乐播放
该命令是实现将等号后面的参数列表传给媒体sample代码入口函数,sample是媒体对外API的封装,用法如下:
-
应用实现中断观察回调的派生类,用户在回调函数中,根据回调参数,做出相应播控操作。
class PlayerInterruptListener : public InterruptListener { void OnInterrupt(int32_t type, int32_t hint) override { if (type == INTERRUPT_TYPE_BEGIN && hint == INTERRUPT_HINT_PAUSE) { // 应用暂停播放 } if (type == INTERRUPT_TYPE_END && hint == INTERRUPT_HINT_RESUME) { // 应用恢复播放 } if (type == INTERRUPT_TYPE_BEGIN && hint == INTERRUPT_HINT_STOP) { // 应用退出播放 } } }; -
激活音频中断。
/* 获取AudioManager实例 */ AudioManager& amIntance = AudioManager::GetInstance(); /* 初始化音频资源*/ amIntance.Initialize(); /* 创建会话ID */ AudioSession sessionId = amIntance.MakeSessionId(); /* 激活音频中断 */ std::shared_ptr<PlayerInterruptListener> playerInterruptListener = std::make_shared<PlayerInterruptListener>(); AudioInterrupt interrupt = { AUDIO_STREAM_MUSIC, sessionId, playerInterruptListener }; amIntance.ActivateAudioInterrupt(interrupt); -
播放音乐,详细内容请参见“本地文件播放示例”小节。
- 如果播放中途收到中断观察回调,证明有其他应用在激活音频中断,需要做出相应播控响应,因此返回到步骤1;正常播放结束则进行步骤5,正常播放结束的流程,请参见“本地文件播放示例”小节。
本地音乐和蓝牙音乐切换
-
请参见“本地音乐播放”小节创建本地音乐播放,如果此时又连接了蓝牙耳机则需要进行设备的切换,设置蓝牙设备已经连接状态。
如果在创建本地音乐播放之前设置蓝牙设备已经连接,则会直接从蓝牙设备输出。
如果蓝牙设备断开,设置蓝牙设备已经断连,则自动切换到本地设备输出。
-
设置蓝牙设备已经连接状态。
-
设置蓝牙设备已经断连状态。
蓝牙语音通话
- 请参见“本地音乐播放”小节中的1,通话应用需要响应相应通话控制。
- 请参见“本地音乐播放”小节的流类型配置,流类型传入AUDIO_STREAM_VOICE_CALL_BT_SCO。
-
设置蓝牙SCO链路输入/输出设备连接状态。
-
初始化通话上下行输入/输出流信息。
/* 通话上下行输入流实例 */ std::shared_ptr<AudioStreamIn> upLinkStreamIn = std::make_shared<AudioStreamIn>(); std::shared_ptr<AudioStreamIn> downLinkStreamIn = = std::make_shared<AudioStreamIn>(); /* 通话上下行输出流实例*/ std::shared_ptr<AudioStreamOut> upLinkStreamOut = std::make_shared<AudioStreamOut>(); std::shared_ptr<AudioStreamOut> downLinkStreamOut = std::make_shared<AudioStreamOut>(); /* 初始化通话上行输入流信息(Codec:mSBC)*/ CapturerInputConfig upLinkCaptureConfig = {}; upLinkCaptureConfig.audioFormat = mSBC; upLinkCaptureConfig.channelCount = AUDIO_CHANNEL_1; upLinkCaptureConfig.sampleRate = AUDIO_SAMPLE_RATE_16K; upLinkCaptureConfig.sessionID = sessionId; upLinkCaptureConfig.streamType = AUDIO_STREAM_VOICE_CALL_BT_SCO; upLinkCaptureConfig.linkDir = AUDIO_UP_LINK; upLinkStreamIn ->Init(upLinkCaptureConfig); /* 初始化通话下行输入流信息*/ CapturerInputConfig downLinkCaptureConfig = {}; downLinkCaptureConfig.sessionID = sessionId; downLinkCaptureConfig.streamType = AUDIO_STREAM_VOICE_CALL_BT_SCO; downLinkCaptureConfig.linkDir = AUDIO_DOWN_LINK; downLinkStreamIn ->Init(downLinkCaptureConfig); /* 初始化通话上行输出流信息*/ AudioRendererConfig upLinkRenderConfig = {}; upLinkRenderConfig.sessionID = g_sessionId; upLinkRenderConfig.streamType = AUDIO_STREAM_VOICE_CALL_BT_SCO; upLinkRenderConfig.linkDir = AUDIO_UP_LINK; upLinkStreamOut ->Init(upLinkRenderConfig); /* 初始化通话下行输出流信息(Codec:mSBC) */ AudioRendererConfig downLinkRenderConfig = {}; downLinkRenderConfig.audioFormat = mSBC; downLinkRenderConfig.channelCount = AUDIO_CHANNEL_1; downLinkRenderConfig.sampleRate = AUDIO_SAMPLE_RATE_16K; downLinkRenderConfig.sessionID = g_sessionId; downLinkRenderConfig.streamType = AUDIO_STREAM_VOICE_CALL_BT_SCO; downLinkRenderConfig.linkDir = linkDir; downLinkStreamOut ->Init(downLinkRenderConfig); -
启动通话上下行输入/输出流,建立绑定关系。
/* 启动通话上行输入/输出流*/ upLinkStreamIn->Start(); upLinkStreamOut->Start(); /* 建立通话上行绑定关系*/ uint32_t frontendId; upLinkStreamIn ->GetCurrentChannelId(frontendId); upLinkStreamOut ->AttachFrontend(frontendId); /* 启动通话下行输入/输出流*/ downLinkStreamIn->Start(); downLinkStreamOut->Start(); /* 建立通话下行绑定关系*/ uint32_t backendId; downLinkStreamOut->GetCurrentChannelId(backendId); downLinkStreamIn ->AttachFrontend(backendId); -
通话结束,停止并销毁通话上下行输入/输出流。
-
设置蓝牙SCO链路输入/输出设备断开状态。
音乐播放中途蓝牙语音通话
交互场景介绍:音乐播放和蓝牙语音通话两个场景交互,音频管理会根据应用类型等决策最终行为,上层只需实现中断监控回调函数即可。
-
请参见“本地音乐播放”小节播放音乐,1 中需要实现中断监控回调函数。
class PlayerInerruptListener : public InterruptListener { void OnInterrupbu(int32_t type, int32_t hint) override { if (type == INTERRUPT_TYPE_BEGIN && hint == INTERRUPT_HINT_PAUSE) { // 当前在媒体回调线程,需要回到应用线程响应播放暂停操作 } if (type == INTERRUPT_TYPE_END && hint == INTERRUPT_HINT_RESUME) { // 当前在媒体回调线程,需要回到应用线程响应播放恢复操作 } if (type == INTERRUPT_TYPE_BEGIN && hint == INTERRUPT_HINT_STOP) { // 当前在媒体回调线程,需要回到应用线程响应播放退出操作 } } }; -
按“蓝牙语音通话”小节建立通话链路时,会触发中断响应函数,type:INTERRUPT_TYPE_BEGIN,hint:INTERRUPT_HINT_PAUSE,播放应用需要响应该操作。
- 按“蓝牙语音通话”小节退出通话链路时,会触发中断响应函数,type:INTERRUPT_TYPE_END,hint:INTERRUPT_HINT_RESUME,播放应用需要响应该操作。
- 请参见“本地文件播放示例”小节等待播放完成并退出播放。
4G VOLTE本地通话
- 请参见“本地音乐播放”小节中的1,通话应用做相应通话控制响应。
- 请参见“本地音乐播放”小节的流类型配置,流类型传入AUDIO_STREAM_VOICE_CALL_VOLTE。
-
设置4G VOLTE 通话,本地输入/输出设备连接状态。
-
初始化通话上下行输入/输出流信息。
/* 创建通话上下行流实例 */ std::shared_ptr<AudioStreamIn> downLinkStreamIn = = std::make_shared<AudioStreamIn>(); std::shared_ptr<AudioStreamOut> upLinkStreamOut = std::make_shared<AudioStreamOut>(); /* 初始化通话上下行流实例 */ CapturerInputConfig captureConfig = {}; captureConfig.audioFormat = PCM; captureConfig.channelCount = 1; captureConfig.linkDir = AUDIO_DOWN_LINK; captureConfig.sampleRate = 16000; captureConfig.sessionID = context->sessionId; captureConfig.streamType = AUDIO_STREAM_VOICE_CALL_VOLTE; downLinkStreamIn->Init(captureConfig); AudioRendererConfig renderConfig = {}; renderConfig.audioFormat = PCM; renderConfig.channelCount = 1; renderConfig.linkDir = AUDIO_UP_LINK; renderConfig.sampleRate = 16000; renderConfig.sessionID = context->sessionId; renderConfig.streamType = AUDIO_STREAM_VOICE_CALL_VOLTE; upLinkStreamOut->Init(renderConfig); -
启动通话上下行输入/输出流。
-
停止/销毁通话上下行输入/输出流。
-
设置4G VOLTE通话,本地输入/输出设备断开状态。
-
场景限制:不支持CAT1本地通话场景和CAT1蓝牙耳机通话场景自动切换;不支持CAT1通话场景和其他场景交互。
用法限制:上述场景需要用户先退出当前场景,再进入新场景。
4G VOLTE蓝牙通话
- 请参见“本地音乐播放”小节中的1,通话应用做相应通话控制响应。
- 请参见“本地音乐播放”小节的流类型配置,流类型传入AUDIO_STREAM_VOICE_CALL_VOLTE。
-
设置4G VOLTE通话,蓝牙输入/输出设备连接状态。
amIntance.SetDeviceConnectionState(IN_BLUETOOTH_SCO, AUDIO_DEVICE_CONNECTED); amIntance.SetDeviceConnectionState(OUT_BLUETOOTH_SCO, AUDIO_DEVICE_CONNECTED); amIntance.SetDeviceConnectionState(IN_MODEM_HEADSET, AUDIO_DEVICE_CONNECTED); amIntance.SetDeviceConnectionState(OUT_MODEM_HEADSET, AUDIO_DEVICE_CONNECTED); -
初始化通话上下行输入/输出流信息。
/* 创建通话上下行流实例 */ std::shared_ptr<AudioStreamIn> downLinkStreamIn = = std::make_shared<AudioStreamIn>(); std::shared_ptr<AudioStreamIn> downLinkStreamOut = = std::make_shared<AudioStreamOut>(); std::shared_ptr<AudioStreamOut> upLinkStreamOut = std::make_shared<AudioStreamOut>(); std::shared_ptr<AudioStreamIn> upLinkStreamIn = = std::make_shared<AudioStreamIn>(); /* 初始化通话上行流实例 */ CapturerInputConfig captureConfig = {}; captureConfig.audioFormat = mSBC; captureConfig.channelCount = 1; captureConfig.linkDir = AUDIO_DOWN_LINK; captureConfig.sampleRate = 16000; captureConfig.sessionID = context->sessionId; captureConfig.streamType = AUDIO_STREAM_VOICE_CALL_VOLTE; downLinkStreamIn->Init(captureConfig); upLinkStreamIn->Init(captureConfig); /* 初始化通话上下行流实例 */ AudioRendererConfig renderConfig = {}; renderConfig.audioFormat = mSBC; renderConfig.channelCount = 1; renderConfig.linkDir = AUDIO_UP_LINK; renderConfig.sampleRate = 16000; renderConfig.sessionID = context->sessionId; renderConfig.streamType = AUDIO_STREAM_VOICE_CALL_VOLTE; upLinkStreamOut->Init(renderConfig); downLinkStreamOut->Init(renderConfig); -
启动通话上下行输入/输出流。
-
停止/销毁通话上下行输入/输出流。
-
设置4G VOLTE通话,本地输入/输出设备断开状态。
amIntance.SetDeviceConnectionState(IN_BLUETOOTH_SCO, AUDIO_DEVICE_DISCONNECTED); amIntance.SetDeviceConnectionState(OUT_BLUETOOTH_SCO, AUDIO_DEVICE_DISCONNECTED); amIntance.SetDeviceConnectionState(IN_MODEM_HEADSET, AUDIO_DEVICE_DISCONNECTED); amIntance.SetDeviceConnectionState(OUT_MODEM_HEADSET, AUDIO_DEVICE_DISCONNECTED); -
场景限制:不支持CAT1本地通话场景和CAT1蓝牙耳机通话场景自动切换;不支持CAT1通话场景和其他场景交互。
用法限制:上述场景需要用户先退出当前场景,再进入新场景。
A2DP Sink蓝牙播放
- 请参见“本地音乐播放”小节中的1,A2DP Sink应用做相应控制响应。
- 请参见“本地音乐播放”小节的流类型配置,流类型传入AUDIO_STREAM_A2DP_MUSIC。
-
设置A2DP sink输入设备连接状态:
-
初始化A2DP sink上下行输入/输出流信息,部分信息可能需要从蓝牙获取。
g_a2dpInputConfig.audioFormat = config.audioFormat; g_a2dpInputConfig.sampleRate = config.sampleRate; g_a2dpInputConfig.channelCount = config.channelCount; g_a2dpInputConfig.streamType = AUDIO_STREAM_A2DP_MUSIC; g_a2dpInputConfig.linkDir = AUDIO_LINK_NONE; upLinkStreamOut->Init(captureConfig); g_a2dpRenderConfig.audioFormat = config.audioFormat; g_a2dpRenderConfig.sampleRate = config.sampleRate; g_a2dpRenderConfig.channelCount = config.channelCount; g_a2dpRenderConfig.streamType = AUDIO_STREAM_A2DP_MUSIC; g_a2dpRenderConfig.linkDir = AUDIO_LINK_NONE; downLinkStreamIn->Init(captureConfig); -
启动A2DP sink上下行输入/输出流。
-
停止/销毁A2DP sink上下行输入/输出流。
-
设置A2DP sink输入设备断开状态。
-
场景限制:不支持A2DP sink播放的时候、再连接蓝牙耳机进行播放。
音频采集场景开发
本节说明音频采集从参数配置、启动、读取数据到停止和退出的调用流程。模块背景、架构和功能说明见多媒体软件背景知识。
错误码
表 1 音频采集API错误码
错误代码 |
宏定义 |
描述 |
|---|---|---|
0xFFFFFFFF |
ERR_INVALID_READ |
读数据失败。 |
0x0 |
SUCCESS |
成功。 |
0x3C10000 |
ERROR |
失败。 |
0x3C10001 |
ERR_ILLEGAL_STATE |
非法状态。 |
0x2A05 |
ERR_INVALID_PARAM |
无效参数。 |
0x3C10003 |
ERR_EARLY_PREPARE |
不涉及。 |
0x3C10004 |
ERR_SOURCE_NOT_SET |
不涉及。 |
0x3C10005 |
ERR_INVALID_OPERATION |
无效操作。 |
0x2A09 |
ERR_NOFREE_CHANNEL |
不涉及。 |
0x3C10007 |
ERR_READ_BUFFER |
不涉及。 |
0x3C10008 |
ERR_NOT_STARTED |
不涉及。 |
0x3C100C8 |
ERR_UNKNOWN |
未知错误。 |
典型场景开发指导
场景接口参考
AudioCapturer类
AudioCapturer 面向录音业务,负责设置采集参数、启动录制、读取 PCM、停止和释放采集资源。采样率、位宽、声道数和输入源应与后续存储、编码或算法要求一致。
接口说明:AudioCapturer API Reference。
RecordingModule类
RecordingModule 封装录音任务的启动、停止和取消控制,适用于需要按业务状态管理录音会话的应用。
数据类型与结构参考
以下类型和结构的完整定义、枚举值及成员说明以 SDK 接口头文件为准。
| 类型或结构 | 用途 |
|---|---|
| AudioCapturer | 提供音频采集器对象的状态和调试信息。 |
| AudioCapturerInfo | 描述采集器的输入设备、采样率和通道等信息。 |
| Timestamp | 表示音频帧对应的时间戳,用于同步播放和采集。 |
| Timebase | 指定音频时间戳使用的时间基准。 |
| State | 表示采集器当前状态,例如准备、运行或停止。 |
| AudioCapturerDebugInfo | 记录采集器的配置、运行状态和错误信息。 |
音频采集
下面sample描述了音频采集模块的创建、提取数据到销毁的过程。
说明: 每种格式的采样率/声道数/位宽是固定的,不支持修改。
上述命令会将等号后面的参数列表传给媒体sample代码入口函数,sample是媒体对外API的封装,用法如下。
音频采集准备工作
/*******************************************************************/
请求音频焦点、创建实例、配置参数
/*******************************************************************/
class CaptureInterruptListener : public InterruptListener {
public:
CaptureInterruptListener(){};
~CaptureInterruptListener(){};
void OnInterrupt(int32_t type, int32_t hint) override
{
if (type == INTERRUPT_TYPE_BEGIN && hint == INTERRUPT_HINT_PAUSE) {
printf("{%s():%d} need app pause audio capture\n", __FUNCTION__, __LINE__);
}
if (type == INTERRUPT_TYPE_END && hint == INTERRUPT_HINT_RESUME) {
printf("{%s():%d} need app resume audio capture\n", __FUNCTION__, __LINE__);
}
if (type == INTERRUPT_TYPE_BEGIN && hint == INTERRUPT_HINT_STOP) {
printf("{%s():%d} need app stop audio capture\n", __FUNCTION__, __LINE__);
}
if (type == INTERRUPT_TYPE_END && hint == INTERRUPT_HINT_STOP) {
printf("{%s():%d} reserved\n", __FUNCTION__, __LINE__);
}
}
};
char filePath[PATH_MAX];
AudioCodecFormat audioFormat;
if (strncasecmp(argv[1], "mp3", strlen("mp3")) == 0) {
audioFormat = MP3;
} else if (strncasecmp(argv[1], "silk", strlen("silk")) == 0) {
audioFormat = SILK;
} else if (strncasecmp(argv[1], "pcm", strlen("pcm")) {
audioFormat = PCM;
}
int32_t sampleRate = atoi(argv[2]);
int32_t channelCount = atoi(argv[3]);
AudioBitWidth bitWidth = (AudioBitWidth)atoi(argv[4]);
if (strlen(argv[5]) < PATH_MAX) {
if (memcpy_s(filePath, PATH_MAX, argv[5], strlen(argv[5]) + 1) != EOK) {
printf("{%s():%d} memcpy_s failed\n", __FUNCTION__, __LINE__);
return -1;
}
} else {
printf("{%s():%d} file path too long\n", __FUNCTION__, __LINE__);
return -1;
}
FILE *pfd = fopen(filePath, "w+");
if (pfd == NULL) {
printf("{%s():%d} fopen failed\n", __FUNCTION__, __LINE__);
return -1;
}
/* 获取AudioManager实例 */
AudioManager& amIntance = AudioManager::GetInstance();
/* 初始化音频资源*/
amIntance.Initialize();
/* 创建会话ID */
AudioSession sessionId = amIntance.MakeSessionId();
/* 激活音频中断 */
std::shared_ptr<CaptureInterruptListener> captureInterruptListener= std::make_shared<CaptureInterruptListener>();
AudioInterrupt interrupt = { AUDIO_STREAM_VOICE_RECORD, sessionId, captureInterruptListener};
amIntance.ActivateAudioInterrupt(interrupt);
AudioCapturer audioCapturer = std::make_shared<AudioCapturer>();
if (audioCapturer == nullptr || audioCapturer.get() == nullptr) {
printf("{%s,%d} audio capturer is nullptr\n", __func__, __LINE__);
(void)amIntance.DeactivateAudioInterrupt(interrupt);
fclose(pfd);
return -1;
}
AudioCapturerInfo info = {};
info.inputSource = AUDIO_MIC;
info.audioFormat = audioFormat;
info.sampleRate = sampleRate;
info.channelCount = channelCount;
info.streamType = AUDIO_STREAM_VOICE_RECORD;
info.bitWidth = bitWidth;
info.sessionID = sessionId;
if (audioCapturer->SetCapturerInfo(info) != 0) {
printf("{%s,%d} set capturer info failed\n", __func__, __LINE__);
(void)amIntance.DeactivateAudioInterrupt(interrupt);
fclose(pfd);
return -1'
}
size_t frameCount = audioCapturer->GetFrameCount();
if (!frameCount) {
printf("{%s,%d} frame count is invalid\n", __func__, __LINE__);
(void)audioCapturer->Release()
(void)amIntance.DeactivateAudioInterrupt(interrupt);
fclose(pfd);
return -1;
}
if (audioFormat == OPUS) {
AudioCapturerOggCtxInit(pfd, info);
}
音频采集启动
/*******************************************************************/
启动处理
/*******************************************************************/
size_t framesize = frameCount * info.channelCount * info.bitWidth / sizeof(uint8_t);
uint8_t *buffer = new uint8_t[framesize];
if (buffer == nullptr) {
printf("{%s,%d} buffer is nullptr\n", __func__, __LINE__);
return -1;
}
if (!audioCapturer->Start()) {
printf("{%s,%d} start failed\n", __func__, __LINE__);
delete g_audioSourceProcessInput.buffer;
g_audioSourceProcessInput.buffer = nullptr;
return -1;
}
音频采集数据提取
/*******************************************************************/
数据提取处理
/*******************************************************************/
int readCnt = 0;
bool needStop = false;
while (true) {
int ret = audioCapturer->Read(buffer, framesize, false);
if (ret == -1) {
printf("{%s():%d} read failed: 0x%x\n", __FUNCTION__, __LINE__, ret);
continue;
}
if (ret == ERR_RETRY_READ) {
usleep(10000);
continue;
}
fwrite(buffer, 1, ret, pfd);
readCnt++;
printf("{%s():%d} read count: %d, size: %d\n", __FUNCTION__, __LINE__, readCnt, ret);
if (needStop) {
break;
}
}
音频采集停止
/*******************************************************************/
停止处理
/*******************************************************************/
if (!audioCapturer->Stop()) {
printf("{%s,%d} stop failed\n", __func__, __LINE__);
}
音频采集退出工作
/*******************************************************************/
退出处理
/*******************************************************************/
if (!audioCapturer->Release()) {
printf("{%s,%d} release failed\n", __func__, __LINE__);
}
delete g_audioSourceProcessInput.buffer;
g_audioSourceProcessInput.buffer = nullptr;
if (amIntance.DeactivateAudioInterrupt(interrupt) != 0) {
printf("{%s,%d} deactivate audio interrupt failed\n", __func__, __LINE__);
}
播放器场景开发
本节按本地文件和 StreamSource 两类场景说明播放器的资源准备、播放和释放流程。模块背景、架构和功能说明见多媒体软件背景知识。
错误码
当前播放接口返回参数有明确注释,无需错误码。
使用指导
场景接口参考
Player类
Player 负责媒体数据源的设置、准备、播放、暂停、停止、进度控制、输出设备选择和资源释放。开发者应遵循 SetSource、Prepare、Play、Stop/Release 的状态机顺序。
接口说明:Player API Reference。
PlayerCallback类
PlayerCallback 用于接收播放完成、错误、信息和跳转完成等异步事件,应用可据此更新 UI 或调度后续业务。
接口说明:PlayerCallback。
Source类
Source 表示播放器的数据源,封装本地文件描述符、URI 或流式数据等来源信息。
接口说明:Player API Reference。
StreamSource类
StreamSource 面向由应用持续提供数据的流式播放场景,负责描述流数据及其回调关系。
接口说明:StreamSourcePlayerService。
StreamCallback类
StreamCallback 用于在流式播放过程中获取可写缓冲区并回填数据,必须遵循缓冲区获取和入队顺序。
接口说明:StreamSourcePlayerService。
AudioModule类
AudioModule 为 JavaScript 播放业务提供播放器状态、数据源、音量、循环和事件回调等封装能力。
接口说明:JS Player API Reference。
数据类型与结构参考
以下类型和结构的完整定义、枚举值及成员说明以 SDK 接口头文件为准。
| 类型或结构 | 用途 |
|---|---|
| PlayerSeekMode | 指定播放器跳转到目标位置时的定位方式。 |
| PlayerStates | 表示播放器当前状态,例如准备、播放、暂停或停止。 |
| PlayerInfoType | 指定播放器信息查询的类型。 |
| PlayerErrorType | 表示播放器错误类别,便于定位失败原因。 |
| SourceType | 表示播放器输入源类型,例如文件、流或内存数据。 |
| BufferFlags | 标识音频或视频缓冲区的有效性和边界状态。 |
| CodecFormat | 表示媒体编解码格式,用于选择对应的解码器。 |
| PlayerDebugInfo | 记录播放器配置、状态和播放进度等调试信息。 |
| PlayerFileDebugInfo | 记录播放器文件源、解析和读取状态。 |
| PlayerVideoDebugInfo | 记录视频轨道参数和视频播放状态。 |
| PlayerAudioDebugInfo | 记录音频轨道参数和音频播放状态。 |
| PlayerControlDebugInfo | 记录播放器控制命令及其执行结果。 |
本地文件播放示例
文件播放一个媒体文件到结尾,用户接口调用说明如下:
-
初始化播放器模块。
/*******************************************************************/ 播放器模块进行初始化并创建一个播放实例。 /*******************************************************************/ std::shared_ptr<Player> player = std::make_shared<Player>(); if (player == nullptr || player.get() == nullptr) { printf("{%s():%d} player is nullptr\n", __func__, __LINE__); return -1; } -
设置播放源,准备播放。
/*******************************************************************/ 设置播放器的数据源,并执行Prepare处理 /*******************************************************************/ std::string uri(xxx.xxx); // xxx.xxx:文件路径 std::map<std::string, std::string> header; Source source(uri, header); player->SetSource(source); player->SetAudioSessionId(sessionId); // 音频文件需要 player->Prepare(); -
设置播放信息。
bool isPlaybackCompleted = false; MediaMutexHandle playerMutex = MediaMutexCreate(NULL); MediaThreadCondHandle playerCond = MediaThreadCondCreate(); class PlayerCallbackImpl : public PlayerCallback { public: PlayerCallbackImpl() {} ~PlayerCallbackImpl() {} void OnPlaybackComplete() { MediaMutexLock(playerMutex); isPlaybackCompleted = true; MediaThreadCondSignal(playerCond); MediaMutexUnLock(playerMutex); } } // 视频文件需要 bool surfaceInitExit = false; bool surfaceInitSuccess = false; MediaMutexHandle surfaceMutex = MediaMutexCreate(NULL); MediaThreadCondHandle surfaceCond = MediaThreadCondCreate(); static void SurfaceInitEvent(void) { RootView::GetInstance()->SetWidth(Screen::GetInstance().GetWidth()); RootView::GetInstance()->SetHeight(Screen::GetInstance().GetHeight()); RootView::GetInstance()->SetPosition(0, 0); RootView::GetInstance()->SetStyle(STYLE_BACKGROUND_OPA, 0); UILiteSurfaceView *surfaceView = new UILiteSurfaceView(); if (surfaceView == nullptr) { printf("{%s():%d} surfaceView is nullptr\n", __FUNCTION__, __LINE__); MediaMutexLock(surfaceMutex); surfaceInitExit = true; surfaceInitSuccess = false; MediaThreadCondSignal(surfaceCond); MediaMutexUnLock(surfaceMutex); return; } surfaceView->SetViewId("sur"); RootView::GetInstance()->Add(surfaceView); Surface *surface = surfaceView->GetSurface(); surface->SetStrideAlignment(64); surface->SetWidthAndHeight(454, 454); // 视频宽高 surface->SetQueueSize(2); surface->SetFormat(PIXEL_FMT_YCBCR_420_SP); surface->SetSize(454 * 454 * 3 / 2); MediaMutexLock(surfaceMutex); surfaceInitExit = true; surfaceInitSuccess = true; MediaThreadCondSignal(surfaceCond); MediaMutexUnLock(surfaceMutex); } GraphicService::GetInstance()->PostGraphicEvent([] { SurfaceInitEvent(); } // 阻塞式等待 Surface 初始化结束 MediaMutexLock(surfaceMutex); while (!surfaceInitExit) { MediaThreadCondWait(surfaceCond, surfaceMutex); } MediaMutexUnLock(surfaceMutex); player->SetVideoSurface(surface); std::shared_ptr<PlayerCallback> pcb = std::make_shared<PlayerCallbackImpl>(); player->SetPlayerCallback(pcb); player->SetVolume(100,100); -
播放直到收到播放到文件尾的消息。
-
停止播放器并销毁播放器。
bool surfaceDeinitExit = false; static void SurfaceDeinitEvent(void) { RootView::GetInstance()->Remove(surfaceView); MediaMutexLock(surfaceMutex); surfaceDeinitExit = true; MediaThreadCondSignal(surfaceCond); MediaMutexUnLock(surfaceMutex); } int32_t ret = player->Stop(); printf("{%s():%d} stop ret: %d\n", __FUNCTION__, __LINE__, ret); ret = player->Reset(); printf("{%s():%d} reset ret: %d\n", __FUNCTION__, __LINE__, ret); ret = player->Release(); printf("{%s():%d} release ret: %d\n", __FUNCTION__, __LINE__, ret); GraphicService::GetInstance()->PostGraphicEvent([] { SurfaceDeinitEvent(); } // 阻塞式等待 Surface 去初始化结束 MediaMutexLock(surfaceMutex); while (!g_videoPlayContext->surfaceDeinitExit) { MediaThreadCondWait(surfaceCond, surfaceMutex); } MediaMutexUnLock(surfaceMutex); if (playerMutex != NULL) { MediaMutexDestroy(&playerMutex); playerMutex = NULL; } if (playerCond != NULL) { MediaThreadCondDestroy(&playerCond); playerCond = NULL; } if (surfaceMutex != NULL) { MediaMutexDestroy(&surfaceMutex); surfaceMutex = NULL; } if (surfaceCond != NULL) { MediaThreadCondDestroy(&surfaceCond); surfaceCond = NULL; } if (surfaceView != NULL) { delete surfaceView; surfaceView = NULL; }
StreamSource播放示例
StreamSource用于AACLC音频裸流和JPEG、H264视频裸流播放,后续初始化、准备和播放流程请参见“本地文件播放示例”。
-
初始化设置播放器模块。
-
设置并准备播放数据源。
/*******************************************************************/ 设置播放器的数据源,并执行Prepare处理 /*******************************************************************/ sample.streamSample = std::make_shared<StreamSourceSample>(); Format formats; formats.PutStringValue(CODEC_MIME, MIME_AUDIO_AAC); Source source(sample.streamSample, formats); sample.adapter->SetSource(source); sample.isThreadRunning = true; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, 0x10000); int32_t ret = pthread_create(&sample.process, &attr, streamProcess, &sample); if (ret != 0) { printf("pthread_create failed %d\n", ret); sample.isThreadRunning = false; return -1; } sample.adapter->Prepare();streamProcess线程处理代码片段:
while (true) { pthread_mutex_lock(&sample->mutex); if (!sample->isThreadRunning) { printf("[%s,%d] thread should exit\n", __func__, __LINE__); pthread_mutex_unlock(&sample->mutex); break; } pthread_mutex_unlock(&sample->mutex); ret = sample->streamSample->GetAvailableBuffer(&buffer); if (ret != 0) { usleep(STEAM_PROCESS_SLEEP_TIME_US); continue; } data = sample->streamSample->GetBufferAddress(buffer.idx); if (data == nullptr) { printf("[%s, %d] get buffer null", __func__, __LINE__); break; } len = (buffer.size < READ_LEN) ? buffer.size : READ_LEN; readLen = fread(data + buffer.offset, 1, len, pFile); if (readLen <= len && readLen > 0) { sample->streamSample->QueueBuffer(buffer.idx, buffer.offset, readLen, 0, 8); } else { sample->streamSample->QueueBuffer(buffer.idx, buffer.offset, readLen, 0, 4); printf("[%s, %d] have render eos", __func__, __LINE__); break; }}
注意事项
- 如果使用了launcher,则播放应用应该通过launcher进入。
- 存在launcher场景,播放应用从非launcher进入,视频画面会被launcher遮盖。
智能语音场景开发
本节提供固定命令词、模糊命令词和文本转语音的调用流程。模块背景、架构和功能说明见多媒体软件背景知识。
使用指导
场景接口参考
ASR 引擎
ASR 引擎负责语音识别的初始化、识别启动、停止和去初始化,并将识别结果和状态通知给应用。
接口说明:ASR Engine API Reference。
TTS 引擎
TTS 引擎负责文本转语音的初始化、文本播报、停止和去初始化,应用可据此组织语音播报生命周期。
接口说明:TTS Engine API Reference。
数据类型与结构参考
以下类型和结构的完整定义、枚举值及成员说明以 SDK 接口头文件为准。
固定命令词识别
启动固定命令词流程如下:
static void AsrEngineListenerTest(AsrEngineEvent eventType, void *data, uint32_t length)
{
switch (eventType) {
case ASR_ENGINE_EVENT_STOP:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_STOP !");
break;
case ASR_ENGINE_EVENT_VOICE_COMMAND:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_COMMAND !");
if (data == nullptr) {
MEDIA_INFO_LOG("data is null!");
return;
}
if (length != sizeof(AsrEngineCmdInfo)) {
MEDIA_INFO_LOG("length is not right!");
return;
}
// PrintfResult((const AsrEngineCmdInfo *)data);
break;
case ASR_ENGINE_EVENT_VOICE_BEGIN:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_BEGIN !");
break;
case ASR_ENGINE_EVENT_VOICE_END:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_END !");
break;
case ASR_ENGINE_EVENT_VOICE_ERROR:
MEDIA_ERR_LOG("ASR_ENGINE_EVENT_VOICE_ERROR !");
break;
default:
MEDIA_INFO_LOG("not support eventType[%d] !", eventType);
break;
}
}
AsrEngineInitParams initParams = {};l
initParams.enableNpu = false; // 设置为false,表示启动固定命令词
initParams.language = ASR_ENGINE_CHINESE;
initParams.workMode = ASR_ENGINE_ALWAYS_WAKE;
initParams.listener = AsrEngineListenerTest;
IntellVoiceAsrEngineInit(&initParams);
IntellVoiceAsrEngineStartRecognition();
销毁固定命令词的流程如下:
模糊命令词识别
启动模糊命令词流程如下:
static void AsrEngineListenerTest(AsrEngineEvent eventType, void *data, uint32_t length)
{
switch (eventType) {
case ASR_ENGINE_EVENT_STOP:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_STOP !");
break;
case ASR_ENGINE_EVENT_VOICE_COMMAND:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_COMMAND !");
if (data == nullptr) {
MEDIA_INFO_LOG("data is null!");
return;
}
if (length != sizeof(AsrEngineCmdInfo)) {
MEDIA_INFO_LOG("length is not right!");
return;
}
// PrintfResult((const AsrEngineCmdInfo *)data);
break;
case ASR_ENGINE_EVENT_VOICE_BEGIN:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_BEGIN !");
break;
case ASR_ENGINE_EVENT_VOICE_END:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_END !");
break;
case ASR_ENGINE_EVENT_VOICE_ERROR:
MEDIA_ERR_LOG("ASR_ENGINE_EVENT_VOICE_ERROR !");
break;
default:
MEDIA_INFO_LOG("not support eventType[%d] !", eventType);
break;
}
}
AsrEngineInitParams initParams = {};l
initParams.enableNpu = true; // 设置为true,表示启动模糊命令词
initParams.language = ASR_ENGINE_CHINESE;
initParams.workMode = ASR_ENGINE_ALWAYS_WAKE;
initParams.listener = AsrEngineListenerTest;
IntellVoiceAsrEngineInit(&initParams);
IntellVoiceAsrEngineStartRecognition();
销毁模糊命令词的流程如下:
固定命令词唤醒模糊命令词
启动固定命令词流程如下:
static void PrintfResult(const AsrEngineCmdInfo *info)
{
switch (info->cmdId) {
case ASR_ENGINE_CMD_APP_CONTROL:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_APP_CONTROL, result appId = %d, appType = %d",
info->appControlInfo.appId, info->appControlInfo.appControlType);
break;
case ASR_ENGINE_CMD_MUSIC_CONTROL:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_MUSIC_CONTROL, result musicControlType = %d",
info->musicControlInfo.musicControlType);
break;
case ASR_ENGINE_CMD_ALARM_CONTROL:
MEDIA_INFO_LOG(
"ASR_ENGINE_CMD_ALARM_CONTROL, result alarmControlType = %d, day = %d, timePeriod = %d, hour = %d, minute = %d",
info->alarmControlInfo.alarmControlType, info->alarmControlInfo.alarmDateTime.day,
info->alarmControlInfo.alarmDateTime.timePeriod, info->alarmControlInfo.alarmDateTime.hour,
info->alarmControlInfo.alarmDateTime.minute);
break;
case ASR_ENGINE_CMD_MOTION_CONTROL:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_MOTION_CONTROL, result motionControlType = %d, motionTarget = %d, value = %f",
info->motionControlInfo.motionControlType, info->motionControlInfo.motionTarget, info->motionControlInfo.value);
break;
case ASR_ENGINE_CMD_TELEPHONE_CONTROL:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_TELEPHONE_CONTROL, result telephoneControlType = %d, telnum = %d",
info->telephoneControlInfo.telephoneControlType, info->telephoneControlInfo.telnum);
break;
case ASR_ENGINE_CMD_TIMER_CONTROL:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_TIMER_CONTROL, result timerControlType = %d, hour = %d, minute = %d, second = %d",
info->timerControlInfo.timerControlType, info->timerControlInfo.hour, info->timerControlInfo.minute,
info->timerControlInfo.second);
break;
case ASR_ENGINE_CMD_DEVICE_CONTROL:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_DEVICE_CONTROL, result deviceControlType = %d",
info->deviceControlInfo.deviceControlType);
break;
case ASR_ENGINE_CMD_WAKEUP_KEYWORD:
MEDIA_INFO_LOG("ASR_ENGINE_CMD_WAKEUP_KEYWORD, result wakeupKeyword = %d",
info->wakeupKeywordInfo.wakeupKeyword);
if (info->wakeupKeywordInfo.wakeupKeyword == ASR_ENGINE_WAKEUP_KEYWORD_XIAO_YI) {
// 设置需要启动模糊命令词的标记
}
break;
default:
MEDIA_INFO_LOG("Unknown identification failed!");
break;
}
}
static void AsrEngineListenerTest(AsrEngineEvent eventType, void *data, uint32_t length)
{
switch (eventType) {
case ASR_ENGINE_EVENT_STOP:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_STOP !");
break;
case ASR_ENGINE_EVENT_VOICE_COMMAND:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_COMMAND !");
if (data == nullptr) {
MEDIA_INFO_LOG("data is null!");
return;
}
if (length != sizeof(AsrEngineCmdInfo)) {
MEDIA_INFO_LOG("length is not right!");
return;
}
PrintfResult((const AsrEngineCmdInfo *)data);
break;
case ASR_ENGINE_EVENT_VOICE_BEGIN:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_BEGIN !");
break;
case ASR_ENGINE_EVENT_VOICE_END:
MEDIA_INFO_LOG("ASR_ENGINE_EVENT_VOICE_END !");
break;
case ASR_ENGINE_EVENT_VOICE_ERROR:
MEDIA_ERR_LOG("ASR_ENGINE_EVENT_VOICE_ERROR !");
break;
default:
MEDIA_INFO_LOG("not support eventType[%d] !", eventType);
break;
}
}
AsrEngineInitParams initParams = {};l
initParams.enableNpu = false; // 设置为false,表示启动固定命令词
initParams.language = ASR_ENGINE_CHINESE;
initParams.workMode = ASR_ENGINE_ALWAYS_WAKE;
initParams.listener = AsrEngineListenerTest;
IntellVoiceAsrEngineInit(&initParams);
IntellVoiceAsrEngineStartRecognition();
根据回调的唤醒词事件里面的标记启动模糊命令词的流程如下:
先销毁固定命令词:
再创建模糊命令词:
AsrEngineInitParams initParams = {};l
initParams.enableNpu = true; // 设置为true,表示启动模糊命令词
initParams.language = ASR_ENGINE_CHINESE;
initParams.workMode = ASR_ENGINE_ALWAYS_WAKE;
initParams.listener = AsrEngineListenerTest;
IntellVoiceAsrEngineInit(&initParams);
IntellVoiceAsrEngineStartRecognition();
最后完成识别以后再销毁模糊命令词:
须知: 销毁固定命令词然后再启动会模糊命令词的流程需要在另一个线程处理,不能在回调函数里面处理。
TTS文本转语音
启动TTS文本转语音播放的流程如下:
static void TtsEngineListenerTest(TtsEngineEvent eventType, const void *data, uint32_t length)
{
switch (eventType) {
case TTS_ENGINE_EVENT_SPEAK_BEGIN:
MEDIA_INFO_LOG("TTS_ENGINE_EVENT_SPEAK_BEGIN !");
break;
case TTS_ENGINE_EVENT_SPEAK_PLAY_COMPLETE:
if (data == nullptr || length == 0) {
MEDIA_INFO_LOG("data is NULL!");
return;
}
// 每一个textID对应的文本,播放完成以后都会上报一个播放完成事件
MEDIA_INFO_LOG("TTS_ENGINE_EVENT_SPEAK_PLAY_COMPLETE !textId = %d", *(int32_t *)data);
break;
case TTS_ENGINE_EVENT_SPEAK_END:
MEDIA_INFO_LOG("TTS_ENGINE_EVENT_SPEAK_END !");
break;
case TTS_ENGINE_EVENT_SPEAK_ERROR:
MEDIA_INFO_LOG("TTS_ENGINE_EVENT_SPEAK_ERROR !");
break;
default:
MEDIA_INFO_LOG("not support eventType[%d] !", eventType);
break;
}
}
TtsEngineInitParams initParams = {};
initParams.listener = TtsEngineListenerTest;
IntellVoiceTtsEngineInit(&initParams);
char *textStr1 = "我要播放第一个音频。"; // 注意此处的格式只能是GBK或者GB2312的,不然会识别错误
IntellVoiceTtsEngineTextSpeak(0,textStr1, strlen(textStr1) + 1);
// 可以再次设置需要播放的文本,会按照顺序播放
char *textStr2 = "我要播放第二个音频。"; // 注意此处的格式只能是GBK或者GB2312的,不然会识别错误
IntellVoiceTtsEngineTextSpeak(1,textStr2, strlen(textStr2) + 1);
// 可以再次设置需要播放的文本,会按照顺序播放
char *textStr3 = "我要播放第三个音频。"; // 注意此处的格式只能是GBK或者GB2312的,不然会识别错误
IntellVoiceTtsEngineTextSpeak(2,textStr3, strlen(textStr3) + 1);
...
// 退出播放,会清理掉所有待播放的文本
IntellVoiceTtsEngineStop();
// 退出以后可以再次进行播放
char *textStr4 = "我要播放第四个音频。"; // 注意此处的格式只能是GBK或者GB2312的,不然会识别错误
IntellVoiceTtsEngineTextSpeak(3,textStr4, strlen(textStr4) + 1);
销毁TTS退出: