老调重弹之ffmpeg解码音频
接着之前的视频解码,在之前的基础上加上音频解码,还是使用SDL。
- 首先找到音频流 与找视频流时一样,在
avformat_find_stream_info
之后,遍历一下AVFormatContext
中的streams
,找到codecpar->codec_type
为AVMEDIA_TYPE_AUDIO
的索引。
else if(pformatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audioStream = i;
}
- 打开解码器 与视频流时一样,创建一个
AVCodecContext
,
paudiocodecContext = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(paudiocodecContext, pformatContext->streams[audioStream]->codecpar);
paudiocodec = avcodec_find_decoder(paudiocodecContext->codec_id);
avcodec_open2(paudiocodecContext, paudiocodec, &opts);
-
SDL打开音频 SDL打开音频设备时,需要提供一些关于音频的参数,如采样率、采样格式、通道数等。 但SDL实际使用时的参数可能与指定的不一样,因此需要把打开音频设备时返回的实际使用的参数保存下来,当解码出来的音频参数与设备使用的参数不一致时,需要进行转换。 另外,还需要提供一下回调函数,但SDL需要音频数据时,就会调用这个回调函数来获取数据。只是为了解码音频,所以简单地使用一个缓冲区直接顺序保存解码得到的音频数据,当SDL通过回调函数来取时,直接从这个缓冲区中读取。
-
解码音频数据 与视频时一样,通过
av_read_frame
从文件中读取packet后,通过packet中的stream_index可知道是音频还是视频。 当时音频时,通过函数avcodec_send_packet
把packet发到解码器,通过avcodec_receive_frame
读取解码的frame。对于音频,一个packet可能包含多个frame,因此需要多次调用avcodec_receive_frame
。 -
音频数据格式转换 音频流中的格式可能与需要的不一致,就要进行转换。 通过
swr_alloc_set_opts
分配一个转换时需要的上下文,然后swr_init
进行初始化,再使用swr_convert
进行转换。 尽可能简单,把转换后的数据直接放入缓冲区,等SDL通过回调来读取。 -
播放音频数据 SDL通过回调函数获得音频数据。直接从音频解码后放入的缓冲区中读取数据给SDL。
paudiocodecContext = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(paudiocodecContext, pformatContext->streams[audioStream]->codecpar);
paudiocodec = avcodec_find_decoder(paudiocodecContext->codec_id);
avcodec_open2(paudiocodecContext, paudiocodec, &opts);
SDL_AudioSpec wanted_spec, got_spec;
wanted_spec.freq = paudiocodecContext->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = paudiocodecContext->channels;
wanted_spec.silence = 0;
wanted_spec.samples = 1024;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = nullptr;
if(SDL_OpenAudio(&wanted_spec, &got_spec) < 0)
{
cerr << "SDL_OpenAudio fail:" << SDL_GetError() << endl;
exit(1);
}
AVFrame audio_wanted_frame;
audio_wanted_frame.format = AV_SAMPLE_FMT_S16;
audio_wanted_frame.sample_rate = got_spec.freq;
audio_wanted_frame.channel_layout = av_get_default_channel_layout(got_spec.channels);
audio_wanted_frame.channels = got_spec.channels;
SwrContext *pswrcontext = nullptr;
uint8_t audio_buff[(192000 * 3) / 2];
uint8_t *paudio_buff = audio_buff;
int audio_buff_size = 0;
//解码后的数据直接简单地放入这个buf中
g_audiobuf.cond = SDL_CreateCond();
g_audiobuf.mutex = SDL_CreateMutex();
g_audiobuf.buf.AllocateBuffer(SIZE_16M);
SDL_PauseAudio(0);
//读取到packet后,
else if(pkt.stream_index == audioStream)
{
iret = avcodec_send_packet(paudiocodecContext, &pkt);
for(;;)
{
iret = avcodec_receive_frame(paudiocodecContext, pframe);
if(iret == 0)
{
pswrcontext = swr_alloc_set_opts(pswrcontext,
audio_wanted_frame.channel_layout, (AVSampleFormat)audio_wanted_frame.format, audio_wanted_frame.sample_rate,
pframe->channel_layout, (AVSampleFormat)pframe->format, pframe->sample_rate,
0, nullptr);
if (!pswrcontext || swr_init(pswrcontext) < 0)
{
cerr << "swr_alloc_set_opts | swr_init fail." << endl;
continue;
}
int dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(pswrcontext, pframe->sample_rate) + pframe->nb_samples,
audio_wanted_frame.sample_rate, audio_wanted_frame.format, AVRounding(1));
int len2 = swr_convert(pswrcontext, &paudio_buff, dst_nb_samples, (const uint8_t**)pframe->data, pframe->nb_samples);
audio_buff_size = audio_wanted_frame.channels * len2 * av_get_bytes_per_sample((AVSampleFormat)audio_wanted_frame.format);
//放及取数据的这部分烂死
for(;;)
{
SDL_LockMutex(g_audiobuf.mutex);
uInt32 ifree = g_audiobuf.buf.GetFree();
if(ifree < audio_buff_size)
{
SDL_CondSignal(g_audiobuf.cond);
SDL_UnlockMutex(g_audiobuf.mutex);
continue;
}
g_audiobuf.buf.Write(paudio_buff, audio_buff_size);
SDL_UnlockMutex(g_audiobuf.mutex);
break;
}
}
else
{
break;
}
}
}
//SDL音频回调函数
static void audio_callback(void *userdata, Uint8* stream, int len)
{
memset(stream, 0, len);
if(bquit)
return;
//放及取数据的这部分烂死
SDL_LockMutex(g_audiobuf.mutex);
while(!bquit)
{
uInt32 idata = g_audiobuf.buf.GetSize();
if(idata < len)
{
SDL_CondWait(g_audiobuf.cond, g_audiobuf.mutex);
}
else
{
break;
}
}
g_audiobuf.buf.Read(stream, len);
SDL_UnlockMutex(g_audiobuf.mutex);
}