下面介绍ffmpeg音频解码并采用SDL2播放的两种方式。
mp4压缩文件数据首先读出avPacket压缩数据包,然后通过解码器,解码出AVframe数据包,AVframe里面就包含了非压缩的视频和音频数据。我们这里只是设计音频的播放。avframe里面的音频数据多数是FLTP格式,而SDL2播放数据是AUDIO_S16SYS数据格式。两种数据完全不一样的组织,FLTP是浮点数据,而SDL要求的是整型数据,还有就是数据在缓存里面左右声道的分布不一样。下面是我的播放器采用两种方式:
1、自己变换FLTP数据
直接看代码,就算把AVframe里面的数据,将浮点变成整形,然后重新排列数据。
//下面这段主要是直接把FLTP格式语音数据变换为适合SDL播放的数据。
//这段代码部分来源于,stackoverflow
/* short *sample_buffer = (short*)malloc(aFrame->nb_samples * 2 * 2); //
memset(sample_buffer, 0, aFrame->nb_samples * 4); //
printf("in_samples = %d\n", aFrame->nb_samples);
int i=0;
float *inputChannel0 = (float*)(aFrame->extended_data[0]);
//extended_data【0】左通道; extended_data【1】为右通道
// 单声道
if( aFrame->channels == 1 ) {
for( i=0; i<aFrame->nb_samples; i++ ) {
float sample = *inputChannel0++;
if( sample < -1.0f ) {
sample = -1.0f;
} else if( sample > 1.0f ) {
sample = 1.0f;
}
sample_buffer[i] = (int16_t)(sample * 32767.0f);
}
} else { // 立体声
float* inputChannel1 = (float*)(aFrame->extended_data[1]);
for( i=0; i<aFrame->nb_samples; i++) {
sample_buffer[i*2] = (int16_t)((*inputChannel0++) * 32767.0f);
sample_buffer[i*2+1] = (int16_t)((*inputChannel1++) * 32767.0f);
}
}
audio_buf=(unsigned char *)sample_buffer;
audio_len=aFrame->nb_samples*4;
while(audio_len>0) //等待SDL读取数据
{
SDL_Delay(1);
} */
2、采用重采样技术把FLTP数据变换成适合SDL2d数据
首先、初始化SDL
int VideoPlayer::openSDL()
{
///打开SDL,并设置播放的格式为:AUDIO_S16SYS 双声道,44100hz
///后期使用ffmpeg解码完音频后,需要重采样成和这个一样的格式,否则播放会有杂音
SDL_AudioSpec *wanted_spec, *spec;
int wanted_nb_channels = 2;
int samplerate = 44100;
// int samplerate = out_sample_rate;
wanted_spec = (SDL_AudioSpec*)SDL_malloc(sizeof(struct SDL_AudioSpec));
wanted_spec->channels = wanted_nb_channels;
wanted_spec->samples = 1024;
wanted_spec->freq = samplerate;
wanted_spec->format = AUDIO_S16SYS; // 具体含义请查看“SDL宏定义”部分
wanted_spec->silence = 0; // 0指示静音
// wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; // 自定义SDL缓冲区大小
wanted_spec->callback = sdlAudioCallBackFunc; // 回调函数
wanted_spec->userdata = this; // 传给上面回调函数的外带数据
//打开音频设备,第二个参数为播放音频的设备,NULL表示默认设备
if (SDL_OpenAudio(wanted_spec, NULL) < 0) {
printf("打开SDL Audio 失败\n");
return -1;
}
return 0;
}
要记住打开SDL设备。
第二、初始化重采样部分。
swrCtx = swr_alloc(); //分配上下文
if (!swrCtx)
{
printf("swr_alloc error \n");
return -1;
}
//下面设置重新采样输入和输出参数
swrCtx = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(audio_tgt_channels),//输出
out_sample_fmt, //编码前你希望的格式
out_sample_rate,//输出
av_get_default_channel_layout(aCodecCtx->channels), //in_channel_layout, //输入
aCodecCtx->sample_fmt,//PCM源文件的采样格式
aCodecCtx->sample_rate, //输入
0, 0);
swr_init(swrCtx);
//中间是解码数据
//1、解码数据输入解码器:avcodec_send_packet(pCodecCtx, packet)
//2、获取解码数据:avcodec_receive_frame(pCodecCtx, pFrame)
int dst_nb_samples =av_rescale_rnd(aFrame->nb_samples,out_sample_rate,//得到变换后采用点数
aFrame->sample_rate, AVRounding(0));
int len= swr_convert(swrCtx, audio_out_buffer, dst_nb_samples, (const uint8_t**)aFrame->data, aFrame->nb_samples); //变换器启动
audio_buf = *audio_out_buffer; //获得输出数据
audio_len = len * av_get_bytes_per_sample(out_sample_fmt)*2; //获取输出音频数据大小
第三、SDL2输出语音
void VideoPlayer::sdlAudioCallBackFunc(void *userdata, Uint8 *stream, int len)
{
VideoPlayer *sd = (VideoPlayer*)userdata;
SDL_memset(stream, 0, len);
//缓存数据已播放完毕
if(sd->audio_len <= 0)
{
return;
}
//缓存中能播放最大的长度
len = len >sd->audio_len ?sd->audio_len : len;
//将数据混合至声卡设备
SDL_MixAudio(stream, sd->audio_buf, len, SDL_MIX_MAXVOLUME);
sd->audio_buf += len;//当前播放位置更新
sd->audio_len -= len;//缓存剩余长度更新
}
语音解码全部流程介绍完毕!!