分享代码,用QtMultimedia类播放ffmpeg解码的音频

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

    用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 
#include 
#include 
#include 
#include 
#ifdef  __cplusplus
extern "C"{
#endif
#include 
#include 
#include 
#include 
#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;inb_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`


转载于:https://my.oschina.net/phoromeon/blog/671058

你可能感兴趣的:(ffmpeg,人工智能,python)