用ffmpeg对视频编解码也有一段时间了,从最开始的入手,到现在的稍微入门,差不多有half a year 了,最近才把视频编解码这一块吃个一知半解。我是做图像处理的,用的opencv,做的东西有一部分是要用到ffmpeg,因为opencv这东东有部分也是基于ffmpeg二次开发的,两家亲嘛。不过opencv现在已经3.0了,而ffmpeg全部还是C接口,偶尔不稳定的情况时有发生,一旦出现问题,找问题的时间是以天为基数的。
废话不多说,最近在研究音频处理的,比如播放mp3,用qt有直接可以调用的API,但是需要用到phonon,可能是已经封装好了,或者是考虑到平台性,反正就是不想用。感觉还是从底层开始处理比较舒服,最先是用SDL处理解码后的音频,用的雷神的源码,在主线程里可以用,但用到线程里面就失声了(个人用的是QT做的前端,可能是两家消息循环有冲突)。然后网上查一下可以通过QT的API直接播放FFmpeg解码过后的数据,找是找到了,果然网上还是有大神,但是大神是技术级的,好多东西给的不是很详细,具体怎么样,下面是博文地址:
http://www.myexception.cn/other/1856535.html
下面是自己整理的,直接上源码,这里有点有趣的是直接运行的话只有杂音,但是当我打开rhythm box的时候它就播放正常了,这里会不会涉及到一个打开硬件设备的操作,而我没有添加呢?
//#include <QCoreApplication>
#include <QtMultimedia/QAudio>
#include <QtMultimedia/QAudioFormat>
#include <QtMultimedia/QAudioOutput>
#include <QtTest/QTest>
#ifdef __cplusplus
extern "C"{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
#ifdef __cplusplus
}
#endif
#define MAX_AUDIO_FRAME_SIZE 192000
//将需要调整的音频参数封装起来,以防混乱
struct AudioParam{
int64_t channel_layout;
int nb_samples;
int channels;
int sample_rate;
AVSampleFormat sample_fmt;
}audio_in,audio_out;
int main(int argc, char *argv[])
{
//播放音频,定义下面的四个变量是必须的
QAudioFormat format;
QAudioOutput* audio;
QAudioDeviceInfo info;
QIODevice* out;
AVCodecContext* iAcc;
AVCodec* auCodec;
AVFormatContext* iFmtCtx = NULL;
SwrContext* pSwrCtx;
int audioStream = -1;
AVPacket pkt;
AVFrame* pDecoded = av_frame_alloc();
uint8_t* pktdata;
int pktsize;
int frameFinished = 0;
info = QAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice());
av_register_all();
avcodec_register_all();
//正常的ffmpeg寻找解码器的流程
if(avformat_open_input(&iFmtCtx,argv[1],NULL,NULL) < 0)
{
fprintf(stderr,"Could not open format input.\n");
return -1;
}
if(avformat_find_stream_info(iFmtCtx,NULL) < 0)
{
fprintf(stderr,"Could not find stream information.\n");
return -1;
}
for(int i=0;i<iFmtCtx->nb_streams;i++)
{
if(iFmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
audioStream = i;
break;
}
}
if(-1 == audioStream)
{
fprintf(stderr,"Could not find audio stream.\n");
return -1;
}
iAcc = iFmtCtx->streams[audioStream]->codec;
auCodec = avcodec_find_decoder(iAcc->codec_id);
if(!auCodec)
{
fprintf(stderr,"Could not find decoder.\n");
return -1;
}
if(avcodec_open2(iAcc,auCodec,0) < 0)
{
fprintf(stderr,"Could not open decoder.\n");
return -1;
}
int bit_rate = iAcc->bit_rate;
//这里设置的是2倍,网上大神设置的直接是100倍,个人是按照之前SDL的源码设置的,感觉设置太大了
//会造成大的延时,而且会莫名其妙地出现数据下溢
int out_size = MAX_AUDIO_FRAME_SIZE*2;
//音频缓冲区
uint8_t* play_buf = (uint8_t*)av_malloc(out_size);
//这个为什么要这样设置也不是很理解
const double K = 0.0054;
double sleep_time = 0.0;
//QAudioFormat初始化
audio_out.sample_fmt = AV_SAMPLE_FMT_S16;
audio_out.nb_samples = iAcc->frame_size;
audio_out.channel_layout = AV_CH_LAYOUT_STEREO;
audio_out.sample_rate = 44100;
audio_out.channels = av_get_channel_layout_nb_channels(audio_out.channel_layout);
format.setSampleRate(audio_out.sample_rate);
format.setChannelCount(audio_out.channels);
format.setCodec("audio/pcm");
format.setSampleType(QAudioFormat::SignedInt);
format.setSampleSize(16);
format.setByteOrder(QAudioFormat::LittleEndian);
//判断以上设置的参数是否被支持
if(!info.isFormatSupported(format))
{
fprintf(stderr,"Not a supported audio format.\n");
return -1;
}
audio = new QAudioOutput(format);
pSwrCtx = swr_alloc_set_opts(NULL,audio_out.channel_layout,audio_out.sample_fmt,audio_out.sample_rate,
iAcc->channel_layout,iAcc->sample_fmt,iAcc->sample_rate,0,NULL);
if(!pSwrCtx)
{
fprintf(stderr,"Could not set options for resample context.\n");
return -1;
}
av_init_packet(&pkt);
//调用start方法就可以开始播放了,类似于SDL的SDL_AudioPause()
out = audio->start();
swr_init(pSwrCtx);
while(1)
{
int ret = av_read_frame(iFmtCtx,&pkt);
if(ret < 0)
{
break;
}
pktdata = pkt.data;
pktsize = pkt.size;
//解码音频包,由于一个音频包可能包含多个音频帧,所以需要重复解码
while(pktsize >0)
{
ret = avcodec_decode_audio4(iAcc,pDecoded,&frameFinished,&pkt);
if(ret < 0)
{
fprintf(stderr,"Error while decoding.\n");
return -1;
}
//其实这个位置是任意的,只要解码以后有返回值就可以
pktdata += ret;
pktsize -= ret;
if(frameFinished >0)
{
//根据比特率判断需要多大的延时,但个人使用的时候感觉好像没设置对,运行的时候每次解码只播放了一帧音频,
//后面的时间就出现数据下溢,所以用了固定的延时
if(bit_rate >= 1411200){
sleep_time = K * out_size - 1;
//printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
}else if(bit_rate >= 320000 ){
sleep_time = K * out_size + 1.9;
//printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
} else if(bit_rate >= 128000){
sleep_time = K * out_size - 0.3;
//printf( "rate : %d,sleep : %lf \n",rate,sleep_time);
} else {
sleep_time = K * out_size + 0.5;
//printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
}
//memset((char*)play_buf,0,out_size);
//转换成适合设备的音频数据
swr_convert(pSwrCtx,&play_buf,out_size,(const uint8_t**)pDecoded->data,pDecoded->nb_samples);
out->write((char*)play_buf,out_size);
//QTest::qSleep(sleep_time);
QTest::qSleep(37);
}
}
//新版本API,旧版本是av_free_packet(&pkt)
av_packet_unref(&pkt);
}
avformat_close_input(&iFmtCtx);
av_frame_free(&pDecoded);
swr_free(&pSwrCtx);
return 0;
}
我用的是ffmpeg3.0的,上面的代码可以正常编译,运行可以播放声音,但有杂音,有时候播放杂音特别大,而有时候可以听清播放的内容。看大神写的,应该放到线程里面,个人感觉应该是缓冲区里面数据未清零,但使用了memset以后还是没什么变化。个人可以保证SwrContext给出的转换参数是正确的,可能真要使用线程,有可能av_read_frame这个函数需要用较多的时间寻找数据包,就算是只有音频包,函数寻找数据包也是以字节为单位的。用一个队列存放数据包,用一个线程读取数据包再解码。还有一个问题,就是播放的延时还是不知道怎么设置,希望有同好可以给点建议。
如果是初学者,不知道怎么配置,下面给出.pro文件的配置,自己可以参照自己具体的配置作相应更改:
QT += core multimedia testlib
QT -= gui
TARGET = QtAudio
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
LIBS += `pkg-config --libs libavcodec libavformat libswresample libavutil`