音频播放 via FFmpeg
FFmpeg 简介
FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用 LGPL 或 GPL 许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频 / 视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 code 都是从头开发的。
FFmpeg 在 Linux 平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括 Windows、Mac OS X 等。这个项目最早由 Fabrice Bellard 发起,2004 年至 2015 年间由 Michael Niedermayer 主要负责维护。许多 FFmpeg 的开发人员都来自 MPlayer 项目,而且当前 FFmpeg 也是放在 MPlayer 项目组的服务器上。项目的名称来自 MPEG 视频编码标准,前面的 “FF” 代表 “Fast Forward”。
FFmpeg 命令行播放音频
FFmpeg 提供了现成的程序 ffplay 用命令行的方式对音频进行播放。
>ffplay.exe "D:\You're Beautiful.mp3"
ffplay version 3.3.3 Copyright (c) 2003-2017 the FFmpeg developers
Input #0, mp3, from 'D:\You're Beautiful.mp3':
Metadata:
… …
artist : James Blunt
title : You're Beautiful
date : 2006
Duration: 00:03:24.30, start: 0.025057, bitrate: 192 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, s16p, 192 kb/s
Metadata:
encoder : LAME3.96r
10.58 M-A: 0.000 fd=0 aq=27KB vq=0KB sq=0B f=0/0 ← 此处是正在播放的音频信息,会实时变化
FFmpeg + SDL 播放音频
SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用 C 语言写成。SDL 提供了数种控制图像、声音、输入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X 等)的应用软件。目前 SDL 多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。
我们将利用 FFmpeg 进行解码,然后用 SDL 进行播放。
在播放前,可以在控制台输入 ffmpeg -formats 查看支持的音视频格式(muxers / demuxers):
> ffmpeg -formats
ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers
built with gcc 8.2.1 (GCC) 20181017
libavformat 58. 20.100 / 58. 20.100
... ...
File formats:
D. = Demuxing supported
.E = Muxing supported
--
D 3dostr 3DO STR
E 3g2 3GP2 (3GPP2 file format)
E 3gp 3GP (3GPP file format)
D 4xm 4X Technologies
E a64 a64 - video for Commodore 64
D aa Audible AA format files
D aac raw ADTS AAC (Advanced Audio Coding)
DE ac3 raw AC-3
D acm Interplay ACM
D act ACT Voice file format
D adf Artworx Data Format
D adp ADP
D ads Sony PS2 ADS
E adts ADTS AAC (Advanced Audio Coding)
DE adx CRI ADX
... ...
输入ffmpeg -codecs 可以查看支持的编解码器:
> ffmpeg.exe -codecs
ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers
built with gcc 8.2.1 (GCC) 20181017
libavcodec 58. 35.100 / 58. 35.100
... ...
Codecs:
D..... = Decoding supported
.E.... = Encoding supported
..V... = Video codec
..A... = Audio codec
..S... = Subtitle codec
...I.. = Intra frame-only codec
....L. = Lossy compression
.....S = Lossless compression
-------
D.VI.S 012v Uncompressed 4:2:2 10-bit
D.V.L. 4xm 4X Movie
D.VI.S 8bps QuickTime 8BPS video
.EVIL. a64_multi Multicolor charset for Commodore 64 (encoders: a64multi )
.EVIL. a64_multi5 Multicolor charset for Commodore 64, extended with 5th color (colram) (encoders: a64multi5 )
D.V..S aasc Autodesk RLE
D.VIL. aic Apple Intermediate Codec
DEVI.S alias_pix Alias/Wavefront PIX image
DEVIL. amv AMV Video
D.V.L. anm Deluxe Paint Animation
D.V.L. ansi ASCII/ANSI art
DEV..S apng APNG (Animated Portable Network Graphics) image
DEVIL. asv1 ASUS V1
DEVIL. asv2 ASUS V2
... ...
播放流程
播放代码
音频播放需要匹配解码后的音频格式和 Render 要求的音频格式,这个过程就是重采样(resampling),在 音频采集 via FFmpeg 这篇博文中有做过简单介绍。在 FFmpeg中,有两种方法进行重采样:
- 使用 resampler (SwrContext)
- 使用 filter (AVFilterGraph)
盗用 DShow 中的 Filter Graph ╭∩╮(︶︿︶)╭∩╮
本文中的代码基于 FFmpeg 4.1。
- 以下是使用 resampler 进行播放的概要代码,略去各个函数的具体实现和资源释放
audio_stream_idx = open_input_file(audio_file, AVMEDIA_TYPE_AUDIO, &fmt_ctx, &dec_ctx);
hr = init_resampler(dec_ctx, OUT_CHANNELS, OUT_SAMPLE_FMT, OUT_SAMPLE_RATE, &resample_ctx);
hr = init_fifo(&g_fifo, OUT_SAMPLE_FMT, OUT_CHANNELS);
hr = sdl_helper.init(sdl_fill_audio);
hr = sdl_helper.run();
g_out_audio_info.channels = OUT_CHANNELS;
g_out_audio_info.channel_layout = av_get_default_channel_layout(OUT_CHANNELS);
g_out_audio_info.sample_format = OUT_SAMPLE_FMT;
g_out_audio_info.sample_rate = OUT_SAMPLE_RATE;
g_out_audio_info.frame_size = OUT_FRAME_SIZE;
while (_kbhit() == 0) {
hr = audio_process(fmt_ctx, dec_ctx, &g_out_audio_info, g_fifo,
resample_ctx, audio_stream_idx, &g_finished, NULL, on_audio_proc);
if (g_finished)
break;
}
- 以下是使用 filter 进行播放的概要代码,略去各个函数的具体实现和资源释放
audio_stream_idx = open_input_file(audio_file, AVMEDIA_TYPE_AUDIO, &fmt_ctx, &dec_ctx);
sprintf_s(filter_descr, "aresample=%d,aformat=sample_fmts=s16:channel_layouts=%s",
OUT_SAMPLE_RATE, OUT_CHANNELS == 1 ? "mono" : "stereo");
hr = aud_filter.init(dec_ctx, fmt_ctx->streams[audio_stream_idx]->time_base, filter_descr);
hr = init_fifo(&g_fifo, OUT_SAMPLE_FMT, OUT_CHANNELS);
hr = sdl_helper.init(sdl_fill_audio);
hr = sdl_helper.run();
g_out_audio_info.channels =