#include "thread_init.h"
#include "errcode.h"
#include "ai_media.h"
-
+#include "ogg.h"
+#include "cmsis_os2.h"
#ifdef __cplusplus
extern "C" {
#endif
const uint32_t AI_VOICE_BUFFER_SIZE = 0x800; // 2KB buffer for AI voice
const uint32_t AI_VOICE_QUEUE_SIZE = 128; // 队列大小
+// OGG封装相关常量
+const uint32_t MILLSECOND_TO_SECOND = 1000;
+const uint32_t HEADER_STRLEN = 8;
+const uint32_t OPUS_DELAY_TIME_DIVISOR = 0;
+const uint32_t OPUS_DELAY_TIME_DIVIDED = 100;
+const uint32_t OPUS_ID_HEADER_LEN = 19;
+const uint32_t OPUS_COMMENT_HEADER_LEN = 25;
+const uint32_t OPUS_COMMENT_TAIL_LEN = 4;
+const uint32_t OPUS_ENCODE_HEADER_SIZE = 8;
+const uint32_t OPUS_OPUSTAGS_LEN = 12;
+const uint32_t OPUS_SAMPLERATE = 48000;
+
// 默认音频参数
const AudioCodecFormat DEFAULT_AUDIO_FORMAT = MP3;
const uint32_t DEFAULT_CHANNEL_COUNT = 1;
const uint32_t DEFAULT_SAMPLE_RATE = SAMPLERATE_16000;
+static MediaMutexHandle GetGlobalMutex(void);
+
// AI语音音频参数结构体IVoiceAudioParams;
// 定义音频数据回调函数类型
uint32_t timestamp;
} AIVoiceQueueMessage;
+// OGG封装上下文结构体
+typedef struct CaptureOggContext{
+ ogg_stream_state oggStream;
+ int64_t sampleRate;
+ int64_t channels;
+ int64_t granulepos;
+ int64_t packetCount;
+ int32_t serialno;
+ bool isEnd;
+ ogg_page *page;
+ ogg_packet *packet;
+ unsigned char *idHeader;
+ unsigned char *commentHeader;
+} CaptureOggContext;
typedef struct {
AudioSession sessionId;
void* callbackUserData; // 回调函数用户数据
osMessageQueueId_t inputQueue; // 输入播放数据队列
uint32_t queueMsgCount; // 队列消息数量
-// 音频参数
- AudioCodecFormat audioFormat; // 音频格式
- uint32_t channelCount; // 声道数
- uint32_t sampleRate; // 采样率
+// 输入流音频参数
+ AudioCodecFormat inputAudioFormat; // 输入音频格式
+ uint32_t inputChannelCount; // 输入声道数
+ uint32_t inputSampleRate; // 输入采样率
+
+ // 输出流音频参数
+ AudioCodecFormat outputAudioFormat; // 输出音频格式
+ uint32_t outputChannelCount; // 输出声道数
+ uint32_t outputSampleRate; // 输出采样率
+
+ // OGG封装相关
+ CaptureOggContext oggContext; // OGG封装上下文
+ bool useOggEncapsulation; // 是否使用OGG封装
+ bool oggEncapsulationStarted; // OGG封装是否已开始
} AIVoiceContext;
static MediaMutexHandle g_aiVoiceContextMutex = nullptr;
static AIVoiceContext *g_aiVoiceContext = nullptr;
+// OGG相关内存管理函数
+static void MediaMemFree(void **src)
+{
+ if (src == nullptr || *src == nullptr) {
+ MEDIA_ERR_LOG("%s", "don't need to free!");
+ return;
+ }
+ free(*src);
+ *src = nullptr;
+}
+
+static void *MediaMemCalloc(const size_t bytesNum)
+{
+ void *tmp = malloc(bytesNum);
+
+ if (tmp == nullptr) {
+ MEDIA_ERR_LOG("%s", "malloc failed!");
+ return nullptr;
+ }
+
+ if (memset_s(tmp, bytesNum, 0, bytesNum) != EOK) {
+ MEDIA_ERR_LOG("%s", "memset_s failed!");
+ MediaMemFree(&tmp);
+ return nullptr;
+ }
+ return tmp;
+}
+
+// OGG头部初始化函数
+static int32_t AudioCapturerOggHeaderInit(CaptureOggContext *oggCtx, uint32_t channelCount, uint32_t sampleRate)
+{
+ oggCtx->idHeader = static_cast<unsigned char*>(MediaMemCalloc(OPUS_ID_HEADER_LEN));
+ if (oggCtx->idHeader == nullptr) {
+ MEDIA_ERR_LOG("malloc failed!");
+ return -1;
+ }
+
+ if (memcpy_s(oggCtx->idHeader, HEADER_STRLEN, "OpusHead", HEADER_STRLEN) != 0) {
+ MEDIA_ERR_LOG("memcpy failed!");
+ return -1;
+ }
+ oggCtx->idHeader[0x8] = 0x01;
+ oggCtx->idHeader[0x9] = channelCount;
+ uint32_t preSkip = sampleRate * OPUS_DELAY_TIME_DIVISOR / OPUS_DELAY_TIME_DIVIDED;
+ oggCtx->idHeader[0xA] = preSkip & 0xFF;
+ oggCtx->idHeader[0xB] = (preSkip >> 0x8); // 8 for Little-endian byte order
+ oggCtx->idHeader[0xC] = sampleRate & 0xFF;
+ oggCtx->idHeader[0xD] = (sampleRate >> 0x8) & 0xFF; // 8 for Little-endian byte order
+ oggCtx->idHeader[0xE] = (sampleRate >> 0x10) & 0xFF; // 16 for Little-endian byte order
+ oggCtx->idHeader[0xF] = (sampleRate >> 0x18) & 0xFF; // 24 for Little-endian byte order
+
+ oggCtx->commentHeader = static_cast<unsigned char*>(MediaMemCalloc(OPUS_COMMENT_HEADER_LEN));
+ if (oggCtx->commentHeader == nullptr) {
+ MEDIA_ERR_LOG("malloc failed!");
+ return -1;
+ }
+
+ if (memcpy_s(oggCtx->commentHeader, HEADER_STRLEN, "OpusTags", HEADER_STRLEN) != 0) {
+ MEDIA_ERR_LOG("Opus idHeader memcpy failed!");
+ return -1;
+ }
+ const char *vendor = "libogg-1.3.5";
+ uint32_t vendorLen = std::strlen(vendor);
+ oggCtx->commentHeader[0x8] = vendorLen & 0xFF;
+ oggCtx->commentHeader[0x9] = (vendorLen >> 0x8) & 0xFF; // 8 for Little-endian byte order
+ oggCtx->commentHeader[0xA] = (vendorLen >> 0x10) & 0xFF; // 16 for Little-endian byte order
+ oggCtx->commentHeader[0xB] = (vendorLen >> 0x18) & 0xFF; // 24 for Little-endian byte order
+
+ if (memcpy_s(oggCtx->commentHeader + OPUS_OPUSTAGS_LEN, vendorLen, vendor, vendorLen) != 0) {
+ MEDIA_ERR_LOG("Opus commentHeader memcpy failed!");
+ return -1;
+ }
+ return 0;
+}
+
+// OGG上下文初始化函数
+static int32_t AudioCapturerOggCtxInit(CaptureOggContext *oggCtx, uint32_t channelCount, uint32_t sampleRate)
+{
+ oggCtx->packet = (ogg_packet *)MediaMemCalloc(sizeof(ogg_packet));
+ if (oggCtx->packet == nullptr) {
+ MEDIA_ERR_LOG("malloc failed!");
+ return -1;
+ }
+
+ oggCtx->page = (ogg_page *)MediaMemCalloc(sizeof(ogg_page));
+ if (oggCtx->page == nullptr) {
+ MEDIA_ERR_LOG("malloc failed!");
+ return -1;
+ }
+
+ oggCtx->sampleRate = sampleRate;
+ oggCtx->channels = channelCount;
+ oggCtx->granulepos = -1; // -1 means start page
+ oggCtx->packetCount = 0;
+ oggCtx->isEnd = false;
+ oggCtx->serialno = 0x1; // unique serialno
+
+ if (ogg_stream_init(&oggCtx->oggStream, oggCtx->serialno) != 0) {
+ MEDIA_ERR_LOG("ogg stream init failed");
+ return -1;
+ }
+
+ if (AudioCapturerOggHeaderInit(oggCtx, channelCount, sampleRate) != 0) {
+ MEDIA_ERR_LOG("Header init failed!");
+ return -1;
+ }
+
+ // 写入ID头部
+ oggCtx->packet->packet = oggCtx->idHeader;
+ oggCtx->packet->bytes = OPUS_ID_HEADER_LEN;
+ oggCtx->packet->b_o_s = 1; // 1 means begining of ogg stream
+ oggCtx->packet->e_o_s = 0;
+ oggCtx->packet->granulepos = oggCtx->granulepos;
+ oggCtx->packet->packetno = oggCtx->packetCount++;
+ oggCtx->granulepos = 0;
+
+ int32_t idResult = ogg_stream_packetin(&oggCtx->oggStream, oggCtx->packet);
+ MEDIA_INFO_LOG("ID header packetin result: %d", idResult);
+
+ // 写入注释头部
+ oggCtx->packet->packet = oggCtx->commentHeader;
+ oggCtx->packet->bytes = OPUS_COMMENT_HEADER_LEN;
+ oggCtx->packet->b_o_s = 0;
+ oggCtx->packet->e_o_s = 0;
+ oggCtx->packet->granulepos = 0;
+ oggCtx->packet->packetno = oggCtx->packetCount++;
+
+ int32_t commentResult = ogg_stream_packetin(&oggCtx->oggStream, oggCtx->packet);
+ MEDIA_INFO_LOG("Comment header packetin result: %d", commentResult);
+
+ return 0;
+}
+
+// OGG流刷新函数(修改为调用数据回调)
+static int32_t AudioCaptureOggStreamFlush(CaptureOggContext *oggCtx, AIVoiceDataCallback dataCallback, void* userData)
+{
+ MEDIA_ERR_LOG("AudioCaptureOggStreamFlush");
+ while (ogg_stream_flush(&oggCtx->oggStream, oggCtx->page)) {
+ // 计算页面总大小
+ uint32_t pageSize = oggCtx->page->header_len + oggCtx->page->body_len;
+ uint8_t *pageData = (uint8_t*)malloc(pageSize);
+
+ if (pageData != nullptr) {
+ // 复制页面头部
+ memcpy(pageData, oggCtx->page->header, oggCtx->page->header_len);
+ // 复制页面体
+ memcpy(pageData + oggCtx->page->header_len, oggCtx->page->body, oggCtx->page->body_len);
+
+ // 调用数据回调函数
+ if (dataCallback != nullptr) {
+ dataCallback(pageData, pageSize, userData);
+ }
+
+ free(pageData);
+ } else {
+ MEDIA_ERR_LOG("Failed to allocate memory for OGG page");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+// OGG页面写入函数(修改为调用数据回调)
+static int32_t AudioCaptureWritePage(CaptureOggContext *oggCtx, AIVoiceDataCallback dataCallback, void* userData)
+{
+ if (ogg_stream_pageout(&oggCtx->oggStream, oggCtx->page)) {
+ // 计算页面总大小
+ uint32_t pageSize = oggCtx->page->header_len + oggCtx->page->body_len;
+ uint8_t *pageData = (uint8_t*)malloc(pageSize);
+
+ if (pageData != nullptr) {
+ // 复制页面头部
+ memcpy(pageData, oggCtx->page->header, oggCtx->page->header_len);
+ // 复制页面体
+ memcpy(pageData + oggCtx->page->header_len, oggCtx->page->body, oggCtx->page->body_len);
+
+ // 调用数据回调函数
+ if (dataCallback != nullptr) {
+ dataCallback(pageData, pageSize, userData);
+ }
+
+ free(pageData);
+ } else {
+ MEDIA_ERR_LOG("Failed to allocate memory for OGG page");
+ return -1;
+ }
+ }
+ else
+ {
+ // MEDIA_ERR_LOG("AudioCaptureWritePage");
+ }
+ return 0;
+}
+
+// OGG写入函数(修改为调用数据回调)
+static int32_t AudioCapturerWriteInOGG(CaptureOggContext *oggCtx, uint8_t *buffer, int64_t bufferLen,
+ AIVoiceDataCallback dataCallback, void* userData, bool isEos)
+{
+ if (bufferLen < 0) {
+ MEDIA_ERR_LOG("input buffer invalid");
+ return -1;
+ }
+
+ // 检查缓冲区大小是否足够
+ if (bufferLen < OPUS_ENCODE_HEADER_SIZE) {
+ MEDIA_ERR_LOG("Input buffer too small for OPUS header: %d < %d", bufferLen, OPUS_ENCODE_HEADER_SIZE);
+ return -1;
+ }
+
+ uint32_t opusDataSize = bufferLen - OPUS_ENCODE_HEADER_SIZE;
+
+ oggCtx->packet->packet = (unsigned char *)(buffer + OPUS_ENCODE_HEADER_SIZE);
+ oggCtx->packet->bytes = opusDataSize;
+ oggCtx->packet->b_o_s = 0;
+ oggCtx->packet->e_o_s = isEos ? 1 : 0;
+ oggCtx->packet->granulepos = oggCtx->granulepos;
+ oggCtx->packet->packetno = oggCtx->packetCount++;
+ oggCtx->granulepos += OPUS_SAMPLERATE * 10 / MILLSECOND_TO_SECOND; //capture frequency is 10ms for each frame
+
+ int32_t packetinResult = ogg_stream_packetin(&oggCtx->oggStream, oggCtx->packet);
+ // MEDIA_INFO_LOG("ogg_stream_packetin result: %d", packetinResult);
+
+ if (!isEos) {
+ return AudioCaptureWritePage(oggCtx, dataCallback, userData);
+ } else {
+ MEDIA_INFO_LOG("AudioCaptureOggStreamFlush");
+ return AudioCaptureOggStreamFlush(oggCtx, dataCallback, userData);
+ }
+}
+
+// OGG上下文清理函数
+static void AudioCapturerOggCtxDeInit(CaptureOggContext *oggCtx)
+{
+ if (oggCtx == nullptr) {
+ return;
+ }
+
+ oggCtx->sampleRate = 0;
+ oggCtx->channels = 0;
+ oggCtx->granulepos = 0;
+ oggCtx->packetCount = 0;
+ MediaMemFree((void **)&oggCtx->packet);
+ MediaMemFree((void **)&oggCtx->page);
+ MediaMemFree((void **)&oggCtx->idHeader);
+ MediaMemFree((void **)&oggCtx->commentHeader);
+ ogg_stream_clear(&oggCtx->oggStream);
+}
+
+// 开始OGG封装
+int32_t AI_StartOggEncapsulation(void)
+{
+ MediaMutexHandle mutex = GetGlobalMutex();
+ if (mutex == nullptr) {
+ MEDIA_ERR_LOG("Failed to get global mutex");
+ return -1;
+ }
+
+ MediaMutexLock(mutex);
+ if (g_aiVoiceContext == nullptr) {
+ MEDIA_ERR_LOG("AI voice context is null");
+ MediaMutexUnLock(mutex);
+ return -1;
+ }
+
+ if (!g_aiVoiceContext->useOggEncapsulation) {
+ MEDIA_ERR_LOG("OGG encapsulation not enabled");
+ MediaMutexUnLock(mutex);
+ return -1;
+ }
+
+ if (g_aiVoiceContext->oggEncapsulationStarted) {
+ MEDIA_INFO_LOG("OGG encapsulation already started");
+ MediaMutexUnLock(mutex);
+ return 0;
+ }
+
+ // 初始化OGG上下文
+ int32_t oggRet = AudioCapturerOggCtxInit(&g_aiVoiceContext->oggContext,
+ g_aiVoiceContext->inputChannelCount,
+ g_aiVoiceContext->inputSampleRate);
+ if (oggRet != 0) {
+ MEDIA_ERR_LOG("Failed to initialize OGG context");
+ MediaMutexUnLock(mutex);
+ return -1;
+ }
+
+ g_aiVoiceContext->oggEncapsulationStarted = true;
+ MEDIA_INFO_LOG("OGG encapsulation started successfully");
+
+ MediaMutexUnLock(mutex);
+ return 0;
+}
+
+// 结束OGG封装
+int32_t AI_EndOggEncapsulation(void)
+{
+ MediaMutexHandle mutex = GetGlobalMutex();
+ if (mutex == nullptr) {
+ MEDIA_ERR_LOG("Failed to get global mutex");
+ return -1;
+ }
+
+ MediaMutexLock(mutex);
+ if (g_aiVoiceContext == nullptr) {
+ MEDIA_ERR_LOG("AI voice context is null");
+ MediaMutexUnLock(mutex);
+ return -1;
+ }
+
+ if (!g_aiVoiceContext->useOggEncapsulation || !g_aiVoiceContext->oggEncapsulationStarted) {
+ MEDIA_ERR_LOG("OGG encapsulation not started %d %d",g_aiVoiceContext->useOggEncapsulation,g_aiVoiceContext->oggEncapsulationStarted);
+ MediaMutexUnLock(mutex);
+ return -1;
+ }
+
+ // 完成OGG页面封装
+ int32_t oggRet = AudioCaptureOggStreamFlush(&g_aiVoiceContext->oggContext,
+ g_aiVoiceContext->dataCallback,
+ g_aiVoiceContext->callbackUserData);
+
+ MEDIA_INFO_LOG("OGG page completion result: ret=%d", oggRet);
+
+ // 重新初始化OGG上下文以准备下一页
+ AudioCapturerOggCtxDeInit(&g_aiVoiceContext->oggContext);
+ int32_t reinitRet = AudioCapturerOggCtxInit(&g_aiVoiceContext->oggContext,
+ g_aiVoiceContext->inputChannelCount,
+ g_aiVoiceContext->inputSampleRate);
+ if (reinitRet != 0) {
+ MEDIA_ERR_LOG("Failed to reinitialize OGG context");
+ g_aiVoiceContext->oggEncapsulationStarted = false;
+ }
+
+ MEDIA_INFO_LOG("OGG page completed and context reinitialized");
+
+ MediaMutexUnLock(mutex);
+ return 0;
+}
+
// 全局互斥锁初始化函数(只创建一次,不释放)
static bool InitGlobalMutex(void)
{
context->dataBuffer = nullptr;
}
+ // 清理OGG上下文
+ if (context->useOggEncapsulation) {
+ AudioCapturerOggCtxDeInit(&context->oggContext);
+ MEDIA_INFO_LOG("OGG context cleaned up");
+ }
+
// 清理队列
if (context->inputQueue != NULL) {
// 清空队列中的所有消息
// MEDIA_ERR_LOG("set modem out device connect failed");
// }
- // create downlink AudioStreamIn
+ // create downlink AudioStreamIn (使用输入流参数)
CapturerInputConfig captureConfig = {};
- captureConfig.audioFormat = context->audioFormat;
- captureConfig.channelCount = context->channelCount;
- captureConfig.linkDir = AUDIO_DOWN_LINK;
- captureConfig.sampleRate = context->sampleRate;
+ captureConfig.audioFormat = context->inputAudioFormat;
+ captureConfig.channelCount = context->inputChannelCount;
+ captureConfig.linkDir = AUDIO_UP_LINK;
+ captureConfig.sampleRate = context->inputSampleRate;
captureConfig.sessionID = context->sessionId;
captureConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT;
MEDIA_ERR_LOG("audio stream in init failed");
}
- // create uplink AudioStreamOut
+ // create uplink AudioStreamOut (使用输出流参数)
AudioRendererConfig renderConfig = {};
- renderConfig.audioFormat = context->audioFormat;
- renderConfig.channelCount = context->channelCount;
- renderConfig.linkDir = AUDIO_UP_LINK;
- renderConfig.sampleRate = context->sampleRate;
+ renderConfig.audioFormat = context->outputAudioFormat;
+ renderConfig.channelCount = context->outputChannelCount;
+ renderConfig.linkDir = AUDIO_DOWN_LINK;
+ renderConfig.sampleRate = context->outputSampleRate;
renderConfig.sessionID = context->sessionId;
renderConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT;
int32_t ret = AudioStreamInObtainBuffer(context->dataBuffer, &readSize);
if (ret == 0 && readSize > 0) {
- // 调用音频数据回调函数
- if (context->dataCallback != nullptr) {
- context->dataCallback(context->dataBuffer, readSize, context->callbackUserData);
+ // 如果使用OGG封装
+ if (context->useOggEncapsulation) {
+ // 检查OGG封装是否已开始
+ if (!context->oggEncapsulationStarted) {
+ // OGG封装未开始,跳过处理
+ LOS_Msleep(10); // 10ms
+ continue;
+ }
+
+ // 使用OGG封装处理数据
+ int32_t oggRet = AudioCapturerWriteInOGG(&context->oggContext, context->dataBuffer, readSize,
+ context->dataCallback, context->callbackUserData, false);
+ if (oggRet != 0) {
+ MEDIA_ERR_LOG("OGG encapsulation failed: ret=%d", oggRet);
+ // 如果OGG封装失败,回退到原始数据
+ if (context->dataCallback != nullptr) {
+ context->dataCallback(context->dataBuffer, readSize, context->callbackUserData);
+ }
+ }
+ } else {
+ // 直接调用音频数据回调函数
+ if (context->dataCallback != nullptr) {
+ context->dataCallback(context->dataBuffer, readSize, context->callbackUserData);
+ }
}
// Write audio data to output stream (echo back)
// }
}
- usleep(10000); // 10ms
+
+ LOS_Msleep(10);
}
printf("AI voice thread cleanup completed\r\n");
}
int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData,
- const AIVoiceAudioParams* audioParams)
+ const AIVoiceAudioParams* inputAudioParams,
+ const AIVoiceAudioParams* outputAudioParams,
+ bool useOggEncapsulation)
{
MEDIA_ERR_LOG("StartAIVoice");
app_task_definition_t task_cfg;
g_aiVoiceContext->dataCallback = dataCallback;
g_aiVoiceContext->callbackUserData = userData;
- // 设置音频参数(如果未传入则使用默认值)
- if (audioParams != nullptr) {
- g_aiVoiceContext->audioFormat = (AudioCodecFormat)audioParams->audioFormat;
- g_aiVoiceContext->channelCount = audioParams->channelCount;
- g_aiVoiceContext->sampleRate = audioParams->sampleRate;
- MEDIA_INFO_LOG("Using custom audio params: format=%d, channels=%u, sampleRate=%u",
- audioParams->audioFormat, audioParams->channelCount, audioParams->sampleRate);
+ // 设置输入流音频参数(如果未传入则使用默认值)
+ if (inputAudioParams != nullptr) {
+ g_aiVoiceContext->inputAudioFormat = (AudioCodecFormat)inputAudioParams->audioFormat;
+ g_aiVoiceContext->inputChannelCount = inputAudioParams->channelCount;
+ g_aiVoiceContext->inputSampleRate = inputAudioParams->sampleRate;
+ MEDIA_INFO_LOG("Using custom input audio params: format=%d, channels=%u, sampleRate=%u",
+ inputAudioParams->audioFormat, inputAudioParams->channelCount, inputAudioParams->sampleRate);
} else {
- g_aiVoiceContext->audioFormat = DEFAULT_AUDIO_FORMAT;
- g_aiVoiceContext->channelCount = DEFAULT_CHANNEL_COUNT;
- g_aiVoiceContext->sampleRate = DEFAULT_SAMPLE_RATE;
- MEDIA_INFO_LOG("Using default audio params: format=%d, channels=%u, sampleRate=%u",
+ g_aiVoiceContext->inputAudioFormat = DEFAULT_AUDIO_FORMAT;
+ g_aiVoiceContext->inputChannelCount = DEFAULT_CHANNEL_COUNT;
+ g_aiVoiceContext->inputSampleRate = DEFAULT_SAMPLE_RATE;
+ MEDIA_INFO_LOG("Using default input audio params: format=%d, channels=%u, sampleRate=%u",
DEFAULT_AUDIO_FORMAT, DEFAULT_CHANNEL_COUNT, DEFAULT_SAMPLE_RATE);
}
+ // 设置输出流音频参数(如果未传入则使用默认值)
+ if (outputAudioParams != nullptr) {
+ g_aiVoiceContext->outputAudioFormat = (AudioCodecFormat)outputAudioParams->audioFormat;
+ g_aiVoiceContext->outputChannelCount = outputAudioParams->channelCount;
+ g_aiVoiceContext->outputSampleRate = outputAudioParams->sampleRate;
+ MEDIA_INFO_LOG("Using custom output audio params: format=%d, channels=%u, sampleRate=%u",
+ outputAudioParams->audioFormat, outputAudioParams->channelCount, outputAudioParams->sampleRate);
+ } else {
+ g_aiVoiceContext->outputAudioFormat = DEFAULT_AUDIO_FORMAT;
+ g_aiVoiceContext->outputChannelCount = DEFAULT_CHANNEL_COUNT;
+ g_aiVoiceContext->outputSampleRate = DEFAULT_SAMPLE_RATE;
+ MEDIA_INFO_LOG("Using default output audio params: format=%d, channels=%u, sampleRate=%u",
+ DEFAULT_AUDIO_FORMAT, DEFAULT_CHANNEL_COUNT, DEFAULT_SAMPLE_RATE);
+ }
+
+ // 设置OGG封装参数
+ g_aiVoiceContext->useOggEncapsulation = useOggEncapsulation;
+ g_aiVoiceContext->oggEncapsulationStarted = false;
+ if (g_aiVoiceContext->useOggEncapsulation) {
+ MEDIA_INFO_LOG("OGG encapsulation enabled by parameter");
+ // OGG上下文将在开始接口调用时初始化
+ } else {
+ MEDIA_INFO_LOG("OGG encapsulation disabled by parameter");
+ }
+
MediaMutexUnLock(mutex);
MediaThreadJoin(&g_aiVoiceContext->taskHandle);
}
// 使用示例:
-// 1. 使用默认参数
-// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, nullptr);
+// 1. 使用默认参数(输入流和输出流都使用默认参数,不使用OGG封装)
+// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, nullptr, nullptr, false);
//
-// 2. 使用自定义参数
-// AIVoiceAudioParams audioParams = {
+// 2. 只自定义输入流参数,输出流使用默认参数,不使用OGG封装
+// AIVoiceAudioParams inputParams = {
// .audioFormat = MP3,
// .channelCount = 1,
// .sampleRate = 16000
// };
-// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &audioParams);
+// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &inputParams, nullptr, false);
+//
+// 3. 分别自定义输入流和输出流参数,不使用OGG封装
+// AIVoiceAudioParams inputParams = {
+// .audioFormat = PCM,
+// .channelCount = 1,
+// .sampleRate = 16000
+// };
+// AIVoiceAudioParams outputParams = {
+// .audioFormat = MP3,
+// .channelCount = 2,
+// .sampleRate = 44100
+// };
+// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &inputParams, &outputParams, false);
+//
+// 4. 使用OGG封装(需要手动控制开始和结束)
+// AIVoiceAudioParams inputParams = {
+// .audioFormat = OPUS,
+// .channelCount = 1,
+// .sampleRate = 16000
+// };
+// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &inputParams, nullptr, true);
+// // 开始OGG封装
+// AI_StartOggEncapsulation();
+// // 处理音频数据...
+// // 结束OGG封装
+// AI_EndOggEncapsulation();
+// 注意:当useOggEncapsulation为true时,需要手动调用开始和结束接口来控制OGG封装
//
// 队列使用示例:
// uint8_t audioData[] = {0x01, 0x02, 0x03, 0x04};