前言
本文是流程分析的第四篇,分析ijkPlayer中的音频解码流程,在audio_thread中,如下流程图中所示。
音频帧是如何解码的、如何入队的
音频帧的解码操作是在audio_thread线程中,audio_thread从packet_queue中读取了音频packet,并软解码成音频帧,放入frame_queue中。
音频帧只有软解码,无硬解码。
static int stream_component_open(FFPlayer *ffp, int stream_index) {
// 调用SDL_AoutOpenAudio打开音频设备
audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt);
decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec");
}
static int audio_thread(void *arg) {
int ret = 0;
int got_frame = 0;
AVRational tb;
AVFrame *frame = av_frame_alloc();
Frame *af;
do {
// 软解码获取一帧
got_frame = decoder_decode_frame(ffp, &is->auddec, frame, NULL);
if (!got_frame) {
continue;
}
tb = (AVRational) {
1, frame->sample_rate}; // 时间基
af = frame_queue_peek_writable(&is->sampq); // 获取一个可写的结点
af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); // 赋值pts,用于音视频同步
af->pos = frame->pkt_pos;
af->serial = is->auddec.pkt_serial;
af->duration = av_q2d((AVRational) {
frame->nb_samples, frame->sample_rate});
av_frame_move_ref(af->frame, frame); // 将frame完全赋值给af->frame
frame_queue_push(&is->sampq); // 存入队列
} while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
return ret;
}
// 解码器解码一帧
static int decoder_decode_frame(FFPlayer *ffp, Decoder *d, AVFrame *frame, AVSubtitle *sub) {
int ret = AVERROR(EAGAIN);
for (;;) {
AVPacket pkt;
// avcodec_receive_frame软解码,读取一帧解码后的数据frame
if (d->queue->serial == d->pkt_serial) {
// 流连续的情况
do {
ret = avcodec_receive_frame(d->avctx, frame); // 获取到解码后的AVFrame数据,(即使还没送入新的Packet,这是为了兼容一个Packet可以解出多个Frame的情况)
if (ret == AVERROR_EOF) {
return 0;
}
if (ret >= 0)
return 1;
} while (ret != AVERROR(EAGAIN));
}
do {
if (d->queue->nb_packets == 0)
SDL_CondSignal(d->empty_queue_cond);
if (d->packet_pending) {
// packet_pending,用于在send失败时重新发送,将d->pkt赋值给当前pkt
av_packet_move_ref(&pkt, &d->pkt);
d->packet_pending = 0;
} else {
// 阻塞等待直到退出或者有AVPacket数据,等待read_thread存数据进来
if (packet_queue_get(d->queue, &pkt, &d->pkt_serial, &d->finished) < 0){
return -1;
}
}
} while (d->queue->serial != d->pkt_serial); // 跳过不同的serial,读取相同serial的packet
if (pkt.data == flush_pkt.data) {
// serial变动,Reset the internal decoder state / flush internal buffers.
avcodec_flush_buffers(d->avctx);
d->finished = 0;
d->next_pts = d->start_pts;
d->next_pts_tb = d->start_pts_tb;
} else {
// 将packet送入解码器进行解码
if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
// eagain,需要重新发送,设置packet_pending为true
d->packet_pending = 1;
av_packet_move_ref(&d->pkt, &pkt); // 把pkt赋值给d->pk
av_packet_unref(&pkt);
}
}
}
音频帧如何被读取的、如何被播放的
播放流程前面一篇已经讲过了,此处只写音频队列frame_queue取数据的过程。
在stream_open函数中调用了audio_open打开了音频设备,并设置了sdl_audio_callback
回调函数,播放线程aout_thread在播放时通过回调从frame_queue中获取数据,进行播放。
// ff_ffplay.c, stream是buffer,向里填充数据,write到AudioTrack里去
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) {
int audio_size, len1;
while (len > 0) {
if (is->audio_buf_index >= is->audio_buf_size) {
// audio_buf消耗完了,调用audio_decode_frame重新填充audio_buf
audio_size = audio_decode_frame(ffp);
is->audio_buf_size = audio_size;
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;
if (len1 > len)
len1 = len;
// 读取数据到stream中
if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME) {
memcpy(stream, (uint8_t *) is->audio_buf + is->audio_buf_index, len1);
} else {
memset(stream, 0, len1);
if (!is->muted && is->audio_buf)
SDL_MixAudio(stream, (uint8_t *) is->audio_buf + is->audio_buf_index, len1,
is->audio_volume);
}
// 向后移动,len>0则继续读取数据
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
}
static int audio_decode_frame(FFPlayer *ffp) {
Frame *af;
do {
// 获取一个可读结点,读取新的音频帧
if (!(af = frame_queue_peek_readable(&is->sampq))) {
return -1;
}
// 移动指针指向下一个
frame_queue_next(&is->sampq);
} while (af->serial != is->audioq.serial);
if () {
// 重采样
} else {
// 把这一帧数据赋值给audio_buf
is->audio_buf = af->frame->data[0];
resampled_data_size = data_size;
}
// 更新audio_clock
is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
return resampled_data_size;
}
参考:
ffplay解码线程分析