FFMPEG + SDL音频播放分析
2024-07-02 04:10:56 232人阅读
目录 [hide]
- 1 抽象流程:
- 2 关键实现:
- 2.1 main()函数
- 2.2 decode_thread()读取文件信息和音频包
- 2.3 stream_component_open():设置音频参数和打开设备
- 2.4 audio_callback(): 回调函数,向SDL缓冲区填充数据
- 2.5 audio_decode_frame():解码音频
- 3 FFMPEG结构体
- 4 FFMPEG宏定义
- 4.1 Audio channel convenience macros
- 5 SDL宏定义
抽象流程:
设置SDL的音频参数 —-> 打开声音设备,播放静音 —-> ffmpeg读取音频流中数据放入队列 —-> SDL调用用户设置的函数来获取音频数据 —-> 播放音频
SDL内部维护了一个buffer来存放解码后的数据,这个buffer中的数据来源是我们注册的回调函数(audio_callback),audio_callback调用audio_decode_frame来做具体的音频解码工作,需要引起注意的是:从流中读取出的一个音频包(avpacket)可能含有多个音频桢(avframe),所以需要多次调用avcodec_decode_audio4来完成整个包的解码,解码出来的数据存放在我们自己的缓冲中(audio_buf2)。SDL每一次回调都会引起数据从audio_buf2拷贝到SDL内部缓冲区,当audio_buf2中的数据大于SDL的缓冲区大小时,需要分多次拷贝。
关键实现:
main()函数
1 | int main( int argc, char **argv){ |
4 | is = (VideoState*) av_mallocz( sizeof (VideoState)); |
6 | fprintf (stderr, "Usage: play <file>\n" ); |
11 | if (SDL_Init(SDL_INIT_AUDIO)){ |
12 | fprintf (stderr, "Count not initialize SDL - %s\n" , SDL_GetError()); |
15 | is_strlcpy(is->filename, argv[1], sizeof (is->filename)); |
17 | is->parse_tid = SDL_CreateThread(decode_thread, is); |
19 | SDL_WaitEvent(&event); |
decode_thread()读取文件信息和音频包
1 | static int decode_thread( void *arg){ |
2 | VideoState *is = (VideoState*)arg; |
3 | AVFormatContext *ic = NULL; |
4 | AVPacket pkt1, *packet = &pkt1; |
5 | int ret, i, audio_index = -1; |
8 | global_video_state = is; |
10 | if (avFormat_open_input(&ic, is->filename, NULL, NULL) != 0) { |
11 | fprintf (stderr, "open file error: %s\n" , is->filename); |
15 | if (avformat_find_stream_info(ic, NULL) < 0){ |
16 | fprintf (stderr, "find stream info error\n" ); |
19 | av_dump_format(ic, 0, is->filename, 0); |
20 | for (i = 0; i < ic->nb_streams; i++){ |
21 | if (ic->streams[i])->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index == -1){ |
26 | if (audio_index >= 0) { |
28 | stream_component_open(is, audio_index); |
30 | if (is->audioStream < 0){ |
31 | fprintf (stderr, "could not open codecs for file: %s\n" , is->filename); |
38 | if (is->audioq.size > MAX_AUDIO_SIZE){ |
42 | ret = av_read_frame(is->ic, packet); |
44 | if (ret == AVERROR_EOF || url_feof(is->ic->pb)) break ; |
45 | if (is->ic->pb && is->ic->pb->error) break ; |
48 | if (packet->stream_index == is->audioStream){ |
49 | packet_queue_put(&is->audioq, packet); |
51 | av_free_packet(packet); |
54 | while (!is->quit) SDL_Delay(100); |
57 | event.type = FF_QUIT_EVENT; |
58 | event.user.data1 = is; |
59 | SDL_PushEvent(&event); |
stream_component_open():设置音频参数和打开设备
1 | int stream_component_open(videoState *is, int stream_index){ |
2 | AVFormatContext *ic = is->ic; |
3 | AVCodecContext *codecCtx; |
8 | SDL_AudioSpec wanted_spec, spec; |
9 | int64_t wanted_channel_layout = 0; |
10 | int wanted_nb_channels; |
13 | const int next_nb_channels[] = { 0, 0, 1, 6, 2, 6, 4, 6 }; |
15 | if (stream_index < 0 || stream_index >= ic->nb_streams) return -1; |
16 | codecCtx = ic->streams[stream_index]->codec; |
17 | wanted_nb_channels = codecCtx->channels; |
18 | if (!wanted_channel_layout || wanted_nb_channels != av_get_channel_layout_nb_channels(wanted_channel_layout)) { |
19 | wanted_channel_layout = av_get_default_channel_lauout(wanted_channel_nb_channels); |
20 | wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX; |
22 | wanted_spec.channels = av_get_channels_layout_nb_channels(wanted_channel_layout); |
23 | wanted_spec.freq = codecCtx->sample_rate; |
24 | if (wanted_spec.freq <= 0 || wanted_spec.channels <=0){ |
25 | fprintf (stderr, "Invaild sample rate or channel count!\n" ); |
28 | wanted_spec.format = AUDIO_S16SYS; |
29 | wanted_spec.silence = 0; |
30 | wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; |
31 | wanted_spec.callback = audio_callback; |
32 | wanted_spec.userdata = http://www.mamicode.com/is; |
36 | while (SDL_OpenAudio(&wanted_spec, &spec) < 0){ |
37 | fprintf (stderr, "SDL_OpenAudio(%d channels): %s\n" , wanted_spec.channels, SDL_GetError()); |
38 | wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)]; |
39 | if (!wanted_spec.channels){ |
40 | fprintf (stderr, "No more channel to try\n" ); |
43 | wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels); |
46 | if (spec.format != AUDIO_S16SYS){ |
47 | fprintf (stderr, "SDL advised audio format %d is not supported\n" , spec.format); |
50 | if (spec.channels != wanted_spec.channels) { |
51 | wanted_channel_layout = av_get_default_channel_layout(spec.channels); |
52 | if (!wanted_channel_layout){ |
53 | fprintf (stderr, "SDL advised channel count %d is not support\n" , spec.channels); |
58 | is->audio_src_fmt = is->audio_tgt_fmt = AV_SAMPLE_FMT_S16; |
59 | is->audio_src_freq = is->audio_tgt_freq = spec.freq; |
60 | is->audio_src_channel_layout = is->audio_tgt_layout = wanted_channel_layout; |
61 | is->audio_src_channels = is->audio_tat_channels = spec.channels; |
63 | codec = avcodec_find_decoder(codecCtx>codec_id); |
64 | if (!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)){ |
65 | fprintf (stderr, "Unsupported codec!\n" ); |
68 | ic->streams[stream_index]->discard = AVDISCARD_DEFAULT; |
69 | is->audioStream = stream_index; |
70 | is->audio_st = ic->streams[stream_index]; |
71 | is->audio_buf_size = 0; |
72 | is->audio_buf_index = 0; |
73 | memset (&is->audio_pkt, 0, sizeof (is->audio_pkt)); |
74 | packet_queue_init(&is->audioq); |
audio_callback(): 回调函数,向SDL缓冲区填充数据
1 | void audio_callback( void *userdata, Uint8 *stream, int len){ |
2 | VideoState *is = (VideoState*)userdata; |
3 | int len1, audio_data_size; |
11 | if (is->audio_buf_index >= is->audio_buf_size){ |
12 | audio_data_size = audio_decode_frame(is); |
14 | is(audio_data_size < 0){ |
15 | is->audio_buf_size = 1024; |
17 | memset (is->audio_buf, 0, is->audio_buf_size); |
19 | is->audio_buf_size = audio_data_size; |
21 | is->audio_buf_index = 0; |
24 | len1 = is->audio_buf_size - is->audio_buf_index; |
25 | if (len1 > len) len1 = len; |
27 | memcpy (stream, (uint8_t*)is->audio_buf + is->audio_buf_index, len1); |
30 | is->audio_buf_index += len1; |
audio_decode_frame():解码音频
1 | int audio_decode_frame(VideoState *is){ |
2 | int len1, len2, decoded_data_size; |
3 | AVPacket *pkt = &is->audio_pkt; |
5 | int64_t dec_channel_layout; |
6 | int wanted_nb_samples, resampled_data_size; |
9 | while (is->audio_pkt_size > 0){ |
11 | if (!(is->audio_frame = avacodec_alloc_frame())){ |
12 | return AVERROR(ENOMEM); |
15 | avcodec_get_frame_defaults(is->audio_frame); |
17 | len1 = avcodec_decode_audio4(is->audio_st_codec, is->audio_frame, got_frame, pkt); |
20 | is->audio_pkt_size = 0; |
23 | is->audio_pkt_data += len1; |
24 | is->audio_pkt_size -= len1; |
25 | if (!got_frame) continue ; |
27 | decoded_data_size = av_samples_get_buffer_size(NULL, |
28 | is->audio_frame_channels, |
29 | is->audio_frame_nb_samples, |
30 | is->audio_frame_format, 1); |
31 | dec_channel_layout = (is->audio_frame->channel_layout && is->audio_frame->channels |
32 | == av_get_channel_layout_nb_channels(is->audio_frame->channel_layout)) |
33 | ? is->audio_frame->channel_layout : av_get_default_channel_layout(is->audio_frame->channels); |
34 | wanted_nb_samples = is->audio_frame->nb_samples; |
35 | if (is->audio_frame->format != is->audio_src_fmt || |
36 | dec_channel_layout != is->audio_src_channel_layout || |
37 | is->audio_frame->sample_rate != is->audio_src_freq || |
38 | (wanted_nb_samples != is->audio_frame->nb_samples && !is->swr_ctx)) { |
39 | if (is->swr_ctx) swr_free(&is->swr_ctx); |
40 | is->swr_ctx = swr_alloc_set_opts(NULL, |
41 | is->audio_tgt_channel_layout, |
45 | is->audio_frame->format, |
46 | is->audio_frame->sample_rate, |
48 | if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) { |
49 | fprintf (stderr, "swr_init() failed\n" ); |
52 | is->audio_src_channel_layout = dec_channel_layout; |
53 | is->audio_src_channels = is->audio_st->codec->channels; |
54 | is->audio_src_freq = is->audio_st->codec->sample_rate; |
55 | is->audio_src_fmt = is->audio_st->codec->sample_fmt; |
59 | const uint8_t **in = ( const uint8_t **)is->audio_frame->extended_data; |
60 | uint8_t *out[] = { is->audio_buf2 }; |
61 | if (wanted_nb_samples != is->audio_frame->nb_samples) { |
62 | if (swr_set_compensation(is->swr_ctx, |
63 | (wanted_nb_samples - is->audio_frame->nb_samples)*is->audio_tgt_freq/is->audio_frame->sample_rate, |
64 | wanted_nb_samples * is->audio_tgt_freq/is->audio_frame->sample_rate) < 0) { |
65 | fprintf (stderr, "swr_set_compensation() failed\n" ); |
69 | len2 = swr_convert(is->swr_ctx, out, |
70 | sizeof (is->audio_buf2)/is->audio_tgt_channels/av_get_bytes_per_sample(is->audio_tgt_fmt), |
71 | in, is->audio_frame->nb_samples); |
73 | fprintf (stderr, "swr_convert() failed\n" ); |
76 | if (len2 == sizeof (is->audio_buf2)/is->audio_tgt_channels/av_get_bytes_per_sample(is->audio_tgt_fmt)) { |
77 | fprintf (stderr, "warning: audio buffer is probably too small\n" ); |
78 | swr_init(is->swr_ctx); |
80 | is->audio_buf = is->audio_buf2; |
81 | resampled_data_size = len2*is->audio_tgt_channels*av_get_bytes_per_sample(is->audio_tgt_fmt); |
83 | resampled_data_size = decoded_data_size; |
84 | is->audio_buf = is->audio_frame->data[0]; |
87 | return resampled_data_size; |
89 | if (pkt->data) av_free_packet(pkt); |
90 | memset (pkt, 0, sizeof (*pkt)); |
91 | if (is->quit) return -1; |
92 | if (packet_queue_get(&is->audioq, pkt, 1) < 0) return -1; |
93 | is->audio_pkt_data = http://www.mamicode.com/pkt->data; |
94 | is->audio_pkt_size = pkt->size; |
FFMPEG结构体
channel_layout_map
5 | } channel_layout_map[] = { |
6 | { "mono" , 1, AV_CH_LAYOUT_MONO }, |
7 | { "stereo" , 2, AV_CH_LAYOUT_STEREO }, |
8 | { "2.1" , 3, AV_CH_LAYOUT_2POINT1 }, |
9 | { "3.0" , 3, AV_CH_LAYOUT_SURROUND }, |
10 | { "3.0(back)" , 3, AV_CH_LAYOUT_2_1 }, |
11 | { "4.0" , 4, AV_CH_LAYOUT_4POINT0 }, |
12 | { "quad" , 4, AV_CH_LAYOUT_QUAD }, |
13 | { "quad(side)" , 4, AV_CH_LAYOUT_2_2 }, |
14 | { "3.1" , 4, AV_CH_LAYOUT_3POINT1 }, |
15 | { "5.0" , 5, AV_CH_LAYOUT_5POINT0_BACK }, |
16 | { "5.0(side)" , 5, AV_CH_LAYOUT_5POINT0 }, |
17 | { "4.1" , 5, AV_CH_LAYOUT_4POINT1 }, |
18 | { "5.1" , 6, AV_CH_LAYOUT_5POINT1_BACK }, |
19 | { "5.1(side)" , 6, AV_CH_LAYOUT_5POINT1 }, |
20 | { "6.0" , 6, AV_CH_LAYOUT_6POINT0 }, |
21 | { "6.0(front)" , 6, AV_CH_LAYOUT_6POINT0_FRONT }, |
22 | { "hexagonal" , 6, AV_CH_LAYOUT_HEXAGONAL }, |
23 | { "6.1" , 7, AV_CH_LAYOUT_6POINT1 }, |
24 | { "6.1" , 7, AV_CH_LAYOUT_6POINT1_BACK }, |
25 | { "6.1(front)" , 7, AV_CH_LAYOUT_6POINT1_FRONT }, |
26 | { "7.0" , 7, AV_CH_LAYOUT_7POINT0 }, |
27 | { "7.0(front)" , 7, AV_CH_LAYOUT_7POINT0_FRONT }, |
28 | { "7.1" , 8, AV_CH_LAYOUT_7POINT1 }, |
29 | { "7.1(wide)" , 8, AV_CH_LAYOUT_7POINT1_WIDE }, |
30 | { "octagonal" , 8, AV_CH_LAYOUT_OCTAGONAL }, |
31 | { "downmix" , 2, AV_CH_LAYOUT_STEREO_DOWNMIX, }, |
FFMPEG宏定义
Audio channel convenience macros
1 | #define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER) |
2 | #define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT) |
3 | #define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY) |
4 | #define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER) |
5 | #define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER) |
6 | #define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY) |
7 | #define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER) |
8 | #define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY) |
9 | #define AV_CH_LAYOUT_2_2 (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT) |
10 | #define AV_CH_LAYOUT_QUAD (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) |
11 | #define AV_CH_LAYOUT_5POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT) |
12 | #define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY) |
13 | #define AV_CH_LAYOUT_5POINT0_BACK (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) |
14 | #define AV_CH_LAYOUT_5POINT1_BACK (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY) |
15 | #define AV_CH_LAYOUT_6POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER) |
16 | #define AV_CH_LAYOUT_6POINT0_FRONT (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) |
17 | #define AV_CH_LAYOUT_HEXAGONAL (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER) |
18 | #define AV_CH_LAYOUT_6POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER) |
19 | #define AV_CH_LAYOUT_6POINT1_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER) |
20 | #define AV_CH_LAYOUT_6POINT1_FRONT (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY) |
21 | #define AV_CH_LAYOUT_7POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) |
22 | #define AV_CH_LAYOUT_7POINT0_FRONT (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) |
23 | #define AV_CH_LAYOUT_7POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) |
24 | #define AV_CH_LAYOUT_7POINT1_WIDE (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) |
25 | #define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) |
26 | #define AV_CH_LAYOUT_OCTAGONAL (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT) |
27 | #define AV_CH_LAYOUT_STEREO_DOWNMIX (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT) |
SDL宏定义
SDL_AudioSpec format
1 | AUDIO_U8 Unsigned 8-bit samples |
2 | AUDIO_S8 Signed 8-bit samples |
3 | AUDIO_U16LSB Unsigned 16-bit samples, in little-endian byte order |
4 | AUDIO_S16LSB Signed 16-bit samples, in little-endian byte order |
5 | AUDIO_U16MSB Unsigned 16-bit samples, in big-endian byte order |
6 | AUDIO_S16MSB Signed 16-bit samples, in big-endian byte order |
7 | AUDIO_U16 same as AUDIO_U16LSB ( for backwards compatability probably) |
8 | AUDIO_S16 same as AUDIO_S16LSB ( for backwards compatability probably) |
9 | AUDIO_U16SYS Unsigned 16-bit samples, in system byte order |
10 | AUDIO_S16SYS Signed 16-bit samples, in system byte order |
git clone https://github.com/lnmcc/musicPlayer.git
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉:
投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。