编译环境:Ubuntu16.04 64位
交叉编译工具:arm-himix200-linux-gcc
我这里使用的是ffmpeg-5.1.2.tar.gz,下载地址点击下载地址。
cd /root/
tar zxvf ffmpeg-5.1.2.tar.gz
cd ffmpeg-5.1.2
mkdir output
./configure --cross-prefix=arm-himix200-linux- --enable-cross-compile --target-os=linux --cc=arm-himix200-linux-gcc --arch=arm --prefix=/root/ffmpeg-5.1.2/output --disable-x86asm --disable-debug --disable-doc --disable-zlib --disable-v4l2-m2m --disable-iconv --disable-network --disable-ffplay --disable-ffprobe --disable-symver --disable-indevs --disable-outdevs --disable-parsers --disable-bsfs --disable-filters --disable-protocols --disable-hwaccels --disable-muxers --disable-demuxers --disable-encoders --disable-decoders --enable-demuxer=aac --enable-demuxer=mp3 --enable-demuxer=wav --enable-decoder=mp3 --enable-decoder=aac --enable-decoder=pcm_alaw --enable-decoder=pcm_bluray --enable-decoder=pcm_dvd --enable-decoder=pcm_f16le --enable-decoder=pcm_f24le --enable-decoder=pcm_f32be --enable-decoder=pcm_f32le --enable-decoder=pcm_f64be --enable-decoder=pcm_f64le --enable-decoder=pcm_lxf --enable-decoder=pcm_mulaw --enable-decoder=pcm_s16be --enable-decoder=pcm_s16be_planar --enable-decoder=pcm_s16le --enable-decoder=pcm_s16le_planar --enable-decoder=pcm_s24be --enable-decoder=pcm_s24daud --enable-decoder=pcm_s24le --enable-decoder=pcm_s24le_planar --enable-decoder=pcm_s32be --enable-decoder=pcm_s32le --enable-decoder=pcm_s32le_planar --enable-decoder=pcm_s64be --enable-decoder=pcm_s64le --enable-decoder=pcm_s8 --enable-decoder=pcm_s8_planar --enable-decoder=pcm_u16be --enable-decoder=pcm_u16le --enable-decoder=pcm_u24be --enable-decoder=pcm_u24le --enable-decoder=pcm_u32be --enable-decoder=pcm_u32le --enable-decoder=pcm_u8 --enable-decoder=pcm_vidc --enable-decoder=pcm_zork --enable-protocol=file --enable-small --enable-muxer=pcm_s16le --extra-cflags="-ffunction-sections -fdata-sections -fsigned-char -Wformat"
make
make install
这样,/root/ffmpeg-5.1.2/output下面就是咱们要的程序,bin目录下ffmpeg可以在开发板上运行,include下是需要的头文件,lib下是需要的静态库,share/ffmpeg/examples是一些可以参考的示例代码。
注意,./configure配置命令可以根据实际的需要进行裁剪,我的项目只需要将mp3、aac和wav解码,因此,只配置了相应的demuxer和decoder。
CFLAGS += -ffunction-sections -fdata-sections
LDFLAGS += -Wl,--gc-sections
GCC链接操作是以section作为最小的处理单元,只要一个section中的某个符号被引用,该section就会被加入到可执行程序中去。因此,GCC在编译时可以使用 -ffunction-sections 和 -fdata-sections 将每个函数或符号创建为一个sections,其中每个sections名与function或data名保持一致。而在链接阶段, -Wl,–gc-sections 指示链接器去掉不用的section(其中-wl, 表示后面的参数 -gc-sections 传递给链接器),这样就能减少最终的可执行程序的大小了。
LIBS += libavdevice.a \
libavfilter.a \
libavformat.a \
libavcodec.a \
libavutil.a \
libswresample.a \
libswscale.a
注意,顺序不要乱,否则会报找不到函数,因为有依赖关系。
不需要用到libavdevice.a(读设备,摄像头或录屏等)、libavfilter.a(加特效,如水印等)和libswscale.a(图像拉伸,像素格式转换等)。
LIBS += libavformat.a \
libavcodec.a \
libavutil.a \
libswresample.a
#ifndef __AUDIO_DECODER__
#define __AUDIO_DECODER__
#ifdef __cplusplus
extern "C" {
#endif
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/channel_layout.h"
#include "libavutil/frame.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "libswresample/swresample.h"
#ifdef __cplusplus
}
#endif
class CAudioDecoder
{
public:
CAudioDecoder();
virtual ~CAudioDecoder(void);
int DecodeOpen(uint codec);
int DecodeClose(void);
int DecodeFrame(uchar *pFrame, int nFrameSize, uchar *pOutBuf, int nBufferSize);
private:
void Decode(uchar *pOutBuf, int nBufferSize, int &nSize);
public:
static int transcode_pcm(const char* inputFile, const char* outputFile);
private:
AVCodec *m_Codec;
AVCodecContext *m_Context;
AVCodecParserContext *m_Parser;
AVPacket *m_Packet;
AVFrame *m_Frame;
struct SwrContext* m_converCtx;
AVChannelLayout m_ChannelLayout;
int m_outBytesPerSample;
uint8_t* m_pData;
int m_nSize;
};
不知道ffmpeg的头文件为什么没有extern “C”,C++和C在链接时的差异。
#define TRANSCODE_SAMPLERATE 8000
#define TRANSCODE_FORMAT AV_SAMPLE_FMT_S16
#define TRANSCODE_CHANNELS 1
#define TRANSCODE_NBSAMPLES 4096
/*
* inputFile input file name
* outputFile output file name
* return decode the length of the data
*/
int CAudioDecoder::transcode_pcm(const char* inputFile, const char* outputFile)
{
CFile file;
bool bRet = file.Open(outputFile, CFile::modeCreate | CFile::modeWrite);
if (!bRet)
return -1;
int ret = -1;
AVCodec* codec = NULL;
AVCodecContext* codecContext = NULL;
AVCodecParameters* codecpar = NULL;
AVFrame* frame = NULL;
AVPacket *pPacket = NULL;
struct SwrContext* converCtx = NULL;
uint8_t** data = NULL;
int streamindex = 0;
int outBytesPerSample = 0;
int allocsize = 0;
AVChannelLayout out_ch_layout = AV_CHANNEL_LAYOUT_MONO;
AVFormatContext* frameContext = avformat_alloc_context();
if (frameContext == NULL) {
goto cleanup;
}
if (avformat_open_input(&frameContext, inputFile, NULL, NULL) != 0) {
goto cleanup;
}
if (avformat_find_stream_info(frameContext, NULL) < 0) {
goto cleanup;
}
streamindex = av_find_best_stream(frameContext, AVMEDIA_TYPE_AUDIO, -1, -1, (const AVCodec**)&codec, -1);
if (!codec) {
goto cleanup;
}
codecContext = avcodec_alloc_context3(codec);
codecpar = frameContext->streams[streamindex]->codecpar;
avcodec_parameters_to_context(codecContext, codecpar);
if (avcodec_open2(codecContext, codec, NULL) < 0) {
goto cleanup;
}
outBytesPerSample = TRANSCODE_CHANNELS * av_get_bytes_per_sample(TRANSCODE_FORMAT);
frame = av_frame_alloc();
swr_alloc_set_opts2(&converCtx, &out_ch_layout, TRANSCODE_FORMAT, TRANSCODE_SAMPLERATE, &codecContext->ch_layout, codecContext->sample_fmt, codecContext->sample_rate, 0, NULL);
swr_init(converCtx);
data = (uint8_t**)av_calloc(1, sizeof(*data));
allocsize = av_samples_alloc(data, NULL, TRANSCODE_CHANNELS, TRANSCODE_NBSAMPLES, TRANSCODE_FORMAT, 0);
pPacket = av_packet_alloc();
while (av_read_frame(frameContext, pPacket) >= 0) {
if (pPacket->stream_index != streamindex)
continue;
if (avcodec_send_packet(codecContext, pPacket) < 0) {
goto cleanup;
}
while (avcodec_receive_frame(codecContext, frame) >= 0) {
ret = swr_convert(converCtx, data, allocsize, (const uint8_t**)frame->data, frame->nb_samples);
if (ret > 0)
file.Write(data[0], ret * outBytesPerSample);
}
av_packet_unref(pPacket);
}
while ((ret = swr_convert(converCtx, data, allocsize, NULL, 0)) > 0) {
file.Write(data[0], ret * outBytesPerSample);
}
cleanup:
if (frameContext) {
avformat_close_input(&frameContext);
avformat_free_context(frameContext);
}
if (codecContext)
avcodec_free_context(&codecContext);
if (pPacket)
av_packet_free(&pPacket);
if (data)
av_freep(&data[0]);
av_freep(&data);
if (frame)
av_frame_free(&frame);
if (converCtx)
swr_free(&converCtx);
if (file.IsOpened()) {
ret = file.GetLength();
file.Close();
}
return ret;
}
注意,CFile是我的项目中的文件类,可以替换成文件IO的API。
以aac为例,由于用到了aac的parser,在交叉编译ffmpeg时选项需要添加选项:
--enable-parser=aac
类似,如果需要支持mp3的解码,在交叉编译ffmpeg时选项需要添加选项:
--enable-parser=mp3
其他格式类似。
#include "AudioDecoder.h"
CAudioDecoder::CAudioDecoder()
{
m_Codec = NULL;
m_Context = NULL;
m_Parser = NULL;
m_Packet = NULL;
m_Frame = NULL;
m_converCtx = NULL;
m_ChannelLayout = AV_CHANNEL_LAYOUT_MONO;
m_outBytesPerSample = 0;
m_pData = NULL;
m_nSize = 0;
}
CAudioDecoder::~CAudioDecoder(void)
{
DecodeClose();
}
int CAudioDecoder::DecodeOpen(uint codec)
{
int ret = -1;
switch (codec) {
case CODEC_AAC:
m_Codec = (AVCodec *)avcodec_find_decoder(AV_CODEC_ID_AAC);
break;
default:
m_Codec = NULL;
break;
}
if (!m_Codec) {
fprintf(stderr, "Codec not found\n");
goto error;
}
m_Context = avcodec_alloc_context3(m_Codec);
if (!m_Context) {
fprintf(stderr, "Could not allocate audio codec context\n");
goto error;
}
ret = avcodec_open2(m_Context, m_Codec, NULL);
if (ret < 0) {
fprintf(stderr, "Could not open codec\n");
goto error;
}
m_Parser = av_parser_init(m_Codec->id);
if (!m_Parser) {
fprintf(stderr, "Parser not found\n");
goto error;
}
m_Packet = av_packet_alloc();
if (!m_Packet) {
fprintf(stderr, "Could not allocate audio packet\n");
goto error;
}
m_Frame = av_frame_alloc();
if (!m_Frame) {
fprintf(stderr, "Could not allocate audio frame\n");
goto error;
}
m_outBytesPerSample = TRANSCODE_CHANNELS * av_get_bytes_per_sample(TRANSCODE_FORMAT);
m_nSize = TRANSCODE_NBSAMPLES * m_outBytesPerSample;
m_pData = (uint8_t *)av_malloc(m_nSize);
return 0;
error:
DecodeClose();
return ret;
}
int CAudioDecoder::DecodeClose(void)
{
av_freep(&m_pData);
if (m_Frame)
av_frame_free(&m_Frame);
if (m_converCtx)
swr_free(&m_converCtx);
if (m_Packet)
av_packet_free(&m_Packet);
if (m_Parser)
av_parser_close(m_Parser);
if (m_Context)
avcodec_free_context(&m_Context);
return 0;
}
int CAudioDecoder::DecodeFrame(uchar *pFrame, int nFrameSize, uchar *pOutBuf, int nBufferSize)
{
if (pFrame == NULL || nFrameSize == 0 || pOutBuf == NULL || nBufferSize == 0) {
return -1;
}
int nSize = 0;
uint8_t *data = pFrame;
int data_size = nFrameSize;
while (data_size > 0) {
int ret = av_parser_parse2(m_Parser, m_Context, &m_Packet->data, &m_Packet->size, data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
fprintf(stderr, "Error while parsing\n");
break;
}
if (m_Packet->size > 0) {
data += ret;
data_size -= ret;
Decode(pOutBuf, nBufferSize, nSize);
} else {
break;
}
}
return nSize;
}
void CAudioDecoder::Decode(uchar *pOutBuf, int nBufferSize, int &nSize)
{
int ret = avcodec_send_packet(m_Context, m_Packet);
if (ret < 0) {
return ;
}
swr_alloc_set_opts2(&m_converCtx, &m_ChannelLayout, TRANSCODE_FORMAT, TRANSCODE_SAMPLERATE, &m_Context->ch_layout, m_Context->sample_fmt, m_Context->sample_rate, 0, NULL);
swr_init(m_converCtx);
while (avcodec_receive_frame(m_Context, m_Frame) >= 0) {
ret = swr_convert(m_converCtx, &m_pData, m_nSize, (const uint8_t**)m_Frame->data, m_Frame->nb_samples);
if (ret > 0) {
if (nBufferSize < nSize + ret * m_outBytesPerSample) {
fprintf(stderr, "nBufferSize=[%d] too small!!!\n", nBufferSize);
break;
}
memcpy(pOutBuf + nSize, m_pData, ret * m_outBytesPerSample);
nSize += ret * m_outBytesPerSample;
}
}
while ((ret = swr_convert(m_converCtx, &m_pData, m_nSize, NULL, 0)) > 0) {
if (nBufferSize < nSize + ret * m_outBytesPerSample) {
fprintf(stderr, "nBufferSize=[%d] too small!!!\n", nBufferSize);
break;
}
memcpy(pOutBuf + nSize, m_pData, ret * m_outBytesPerSample);
nSize += ret * m_outBytesPerSample;
}
return ;
}
注意:CODEC_AAC是我的项目中的aac格式宏,替换成自己的aac/mp3,AV_CODEC_ID_AAC换成相应的枚举即可完成其他格式的解码。
int nPcmSize = CAudioDecoder::transcode_pcm(inputFile, outputFile);
inputFile :需要解码的文件名
outputFile :解码后的文件名
nPcmSize :解码后的文件长度,<=0 失败,>0 成功
创建AAC解码器
CAudioDecoder *decoder = new CAudioDecoder();
decoder->DecodeOpen(CODEC_AAC);
解码
int OutSize = decoder->DecodeFrame(pFrame, nFrameSize, pOutBuf, nBufferSize);
销毁解码器
delete decoder;
decoder = NULL;
解码数据,可以传输m.n帧,m>=0 n>=0
pFrame 输入,待解码的数据
nFrameSize 输入,待解码的数据长度
pOutBuf 输入输出,解码后数据,缓冲区由调用者分配
nBufferSize 输入,传入的缓冲区pOutBuf长度
return 返回解码后缓冲区已使用的数据长度,如果传入数据小于1帧,本地调用会返回0,直到下一次输入的数据达到1帧
注意:对于一组连续的音频数据,不要重复调用创建和销毁解码器。