FFMPEG + SDL音頻播放分析
睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接
目錄 [hide]
抽象流程:
設置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){
2
SDL_Event event;
//SDL事件變量
3
VideoState *is;
// 紀錄視頻及解碼器等信息的大結構體
4
is = (VideoState*) av_mallocz(
sizeof
(VideoState));
5
if
(argc < 2){
6
fprintf
(stderr,
"Usage: play <file>\n"
);
7
exit
(1);
8
}
9
av_register_all();
//注冊所有ffmpeg的解碼器
10
/* 初始化SDL,這里只實用了AUDIO,如果有視頻,好需要SDL_INIT_VIDEO等等 */
11
if
(SDL_Init(SDL_INIT_AUDIO)){
12
fprintf
(stderr,
"Count not initialize SDL - %s\n"
, SDL_GetError());
13
exit
(1);
14
}
15
is_strlcpy(is->filename, argv[1],
sizeof
(is->filename));
16
/* 創建一個SDL線程來做視頻解碼工作,主線程進入SDL事件循環 */
17
is->parse_tid = SDL_CreateThread(decode_thread, is);
18
if
(!is->parse_tid){
19
SDL_WaitEvent(&event);
20
switch
(event.type){
21
case
FF_QUIT_EVENT:
22
case
SDL_QUIT:
23
is->quit = 1;
24
SDL_Quit();
25
exit
(0);
26
break
;
27
default
:
28
break
;
29
}
30
}
31
return
0;
32
}
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;
6
7
is->audioStream = -1;
8
global_video_state = is;
9
/* 使用ffmpeg打開視頻,解碼器等 常規工作 */
10
if
(avFormat_open_input(&ic, is->filename, NULL, NULL) != 0) {
11
fprintf
(stderr,
"open file error: %s\n"
, is->filename);
12
return
-1;
13
}
14
is->ic = ic;
15
if
(avformat_find_stream_info(ic, NULL) < 0){
16
fprintf
(stderr,
"find stream info error\n"
);
17
return
-1;
18
}
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){
22
audio_index = i;
23
break
;
24
}
25
}
26
if
(audio_index >= 0) {
27
/* 所有設置SDL音頻流信息的步驟都在這個函數里完成 */
28
stream_component_open(is, audio_index);
29
}
30
if
(is->audioStream < 0){
31
fprintf
(stderr,
"could not open codecs for file: %s\n"
, is->filename);
32
goto
fail;
33
}
34
/* 讀包的主循環, av_read_frame不停的從文件中讀取數據包(這里只取音頻包)*/
35
for
(;;){
36
if
(is->quit)
break
;
37
/* 這里audioq.size是指隊列中的所有數據包帶的音頻數據的總量,并不是包的數量 */
38
if
(is->audioq.size > MAX_AUDIO_SIZE){
39
SDL_Delay(10);
// 毫秒
40
continue
;
41
}
42
ret = av_read_frame(is->ic, packet);
43
if
(ret < 0){
44
if
(ret == AVERROR_EOF || url_feof(is->ic->pb))
break
;
45
if
(is->ic->pb && is->ic->pb->error)
break
;
46
contiue;
47
}
48
if
(packet->stream_index == is->audioStream){
49
packet_queue_put(&is->audioq, packet);
50
}
else
{
51
av_free_packet(packet);
52
}
53
}
54
while
(!is->quit) SDL_Delay(100);
55
fail: {
56
SDL_Event event;
57
event.type = FF_QUIT_EVENT;
58
event.user.data1 = is;
59
SDL_PushEvent(&event);
60
}
61
return
0;
62
}
stream_component_open():設置音頻參數和打開設備
1
int
stream_component_open(videoState *is,
int
stream_index){
2
AVFormatContext *ic = is->ic;
3
AVCodecContext *codecCtx;
4
AVCodec *codec;
5
/* 在用SDL_OpenAudio()打開音頻設備的時候需要這兩個參數*/
6
/* wanted_spec是我們期望設置的屬性,spec是系統最終接受的參數 */
7
/* 我們需要檢查系統接受的參數是否正確 */
8
SDL_AudioSpec wanted_spec, spec;
9
int64_t wanted_channel_layout = 0;
// 聲道布局(SDL中的具體定義見“FFMPEG結構體”部分)
10
int
wanted_nb_channels;
// 聲道數
11
/* SDL支持的聲道數為 1, 2, 4, 6 */
12
/* 后面我們會使用這個數組來糾正不支持的聲道數目 */
13
const
int
next_nb_channels[] = { 0, 0, 1, 6, 2, 6, 4, 6 };
14
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;
21
}
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"
);
26
return
-1;
27
}
28
wanted_spec.format = AUDIO_S16SYS;
// 具體含義請查看“SDL宏定義”部分
29
wanted_spec.silence = 0;
// 0指示靜音
30
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
// 自定義SDL緩沖區大小
31
wanted_spec.callback = audio_callback;
// 音頻解碼的關鍵回調函數
32
wanted_spec.userdata = is;
// 傳給上面回調函數的外帶數據
33
34
/* 打開音頻設備,這里使用一個while來循環嘗試打開不同的聲道數(由上面 */
35
/* next_nb_channels數組指定)直到成功打開,或者全部失敗 */
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)];
// FFMIN()由ffmpeg定義的宏,返回較小的數
39
if
(!wanted_spec.channels){
40
fprintf
(stderr,
"No more channel to try\n"
);
41
return
-1;
42
}
43
wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels);
44
}
45
/* 檢查實際使用的配置(保存在spec,由SDL_OpenAudio()填充) */
46
if
(spec.format != AUDIO_S16SYS){
47
fprintf
(stderr,
"SDL advised audio format %d is not supported\n"
, spec.format);
48
return
-1;
49
}
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);
54
return
-1;
55
}
56
}
57
/* 把設置好的參數保存到大結構中 */
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;
62
63
codec = avcodec_find_decoder(codecCtx>codec_id);
64
if
(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)){
65
fprintf
(stderr,
"Unsupported codec!\n"
);
66
return
-1;
67
}
68
ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
//具體含義請查看“FFMPEG宏定義”部分
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);
75
SDL_PauseAudio(0);
// 開始播放靜音
76
}
audio_callback(): 回調函數,向SDL緩沖區填充數據
1
void
audio_callback(
void
*userdata, Uint8 *stream,
int
len){
2
VideoState *is = (VideoState*)userdata;
3
int
len1, audio_data_size;
4
5
/* len是由SDL傳入的SDL緩沖區的大小,如果這個緩沖未滿,我們就一直往里填充數據 */
6
while
(len > 0){
7
/* audio_buf_index 和 audio_buf_size 標示我們自己用來放置解碼出來的數據的緩沖區,*/
8
/* 這些數據待copy到SDL緩沖區, 當audio_buf_index >= audio_buf_size的時候意味著我*/
9
/* 們的緩沖為空,沒有數據可供copy,這時候需要調用audio_decode_frame來解碼出更
10
/* 多的楨數據 */
11
if
(is->audio_buf_index >= is->audio_buf_size){
12
audio_data_size = audio_decode_frame(is);
13
/* audio_data_size < 0 標示沒能解碼出數據,我們默認播放靜音 */
14
is(audio_data_size < 0){
15
is->audio_buf_size = 1024;
16
/* 清零,靜音 */
17
memset
(is->audio_buf, 0, is->audio_buf_size);
18
}
else
{
19
is->audio_buf_size = audio_data_size;
20
}
21
is->audio_buf_index = 0;
22
}
23
/* 查看stream可用空間,決定一次copy多少數據,剩下的下次繼續copy */
24
len1 = is->audio_buf_size - is->audio_buf_index;
25
if
(len1 > len) len1 = len;
26
27
memcpy
(stream, (uint8_t*)is->audio_buf + is->audio_buf_index, len1);
28
len -= len1;
29
stream += len1;
30
is->audio_buf_index += len1;
31
}
32
}
audio_decode_frame():解碼音頻
1
int
audio_decode_frame(VideoState *is){
2
int
len1, len2, decoded_data_size;
3
AVPacket *pkt = &is->audio_pkt;
4
int
got_frame = 0;
5
int64_t dec_channel_layout;
6
int
wanted_nb_samples, resampled_data_size;
7
8
for
(;;){
9
while
(is->audio_pkt_size > 0){
10
if
(!is->audio_frame){
11
if
(!(is->audio_frame = avacodec_alloc_frame())){
12
return
AVERROR(ENOMEM);
13
}
14
}
else
15
avcodec_get_frame_defaults(is->audio_frame);
16
17
len1 = avcodec_decode_audio4(is->audio_st_codec, is->audio_frame, got_frame, pkt);
18
/* 解碼錯誤,跳過整個包 */
19
if
(len1 < 0){
20
is->audio_pkt_size = 0;
21
break
;
22
}
23
is->audio_pkt_data += len1;
24
is->audio_pkt_size -= len1;
25
if
(!got_frame)
continue
;
26
/* 計算解碼出來的楨需要的緩沖大小 */
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,
42
is->audio_tgt_fmt,
43
is->audio_tgt_freq,
44
dec_channel_layout,
45
is->audio_frame->format,
46
is->audio_frame->sample_rate,
47
0, NULL);
48
if
(!is->swr_ctx || swr_init(is->swr_ctx) < 0) {
49
fprintf
(stderr,
"swr_init() failed\n"
);
50
break
;
51
}
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;
56
}
57
/* 這里我們可以對采樣數進行調整,增加或者減少,一般可以用來做聲畫同步 */
58
if
(is->swr_ctx) {
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"
);
66
break
;
67
}
68
}
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);
72
if
(len2 < 0) {
73
fprintf
(stderr,
"swr_convert() failed\n"
);
74
break
;
75
}
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);
79
}
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);
82
}
else
{
83
resampled_data_size = decoded_data_size;
84
is->audio_buf = is->audio_frame->data[0];
85
}
86
/* 返回得到的數據 */
87
return
resampled_data_size;
88
}
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 = pkt->data;
94
is->audio_pkt_size = pkt->size;
95
96
}
97
}
FFMPEG結構體
channel_layout_map
1
static
const
struct
{
2
const
char
*name;
3
int
nb_channels;
4
uint64_t layout;
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, },
32
};
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
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成