文档章节

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

phoromeon
 phoromeon
发布于 2016/05/06 19:28
字数 1541
阅读 410
收藏 3

    用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`


本文转载自:http://www.myexception.cn/other/1856535.html

phoromeon
粉丝 1
博文 4
码字总数 2998
作品 0
宁波
私信 提问
QtAV 1.4.2 发布,跨平台高性能音视频播放库

- 提高seek速度。要感谢wm4(mpv主力开发者)的指点 - 优化音视频同步逻辑,软解播放高分辨率高帧率视频完胜其他播放器。其他播放器几乎不能看,包括硬解,除了mpv+vaapi。以后将有更好的优化...

LucasWang
2014/12/29
2.5K
4
最简单的基于FFMPEG+SDL的音频播放器:拆分-解码器和播放器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/leixiaohua1020/article/details/46890259 ===================================================== 最简单的...

雷霄骅
2015/07/17
0
0
Android使用FFmpeg(六)--ffmpeg实现音视频同步播放

关于 Android使用FFmpeg(一)--编译ffmpeg Android使用FFmpeg(二)--Android Studio配置ffmpeg Android使用FFmpeg(三)--ffmpeg实现视频播放 Android使用FFmpeg(四)--ffmpeg实现音频播放(使用A...

天王盖地虎626
01/14
0
0
FFmpeg深入分析之零-基础

FFmpeg是相当强大的多媒体编解码框架,在深入分析其源代码之前必须要有基本的多媒体基础知识,否则其源代码会非常晦涩难懂。本文将从介绍一些基本的多媒体只是,主要是为研读ffmpeg源代码做准...

天下杰论
2015/04/22
0
0
多媒体处理神器 神级播放软件 FFmpeg 你知多少?

我们早已经进入了数字多媒体时代,相信每天都会有数不清的男男女女,在PC手机上播放一部部令人热血贲张的小电影。没错,视频播放早已是信息时代的日常,花样迭出的视频播放器成为了手机电脑中...

局长
2017/09/21
3.7K
10

没有更多内容

加载失败,请刷新页面

加载更多

C 语言 二级指针的使用

#include <stdio.h>#include <stdlib.h>typedef struct node Node;struct node {int data;struct node* next;struct node* prev;};Node head;Node* insert(Node......

小张525
14分钟前
2
0
【大数据技术】——Hadoop(1)

什么是大数据 基本概念 《数据处理》 在互联网技术发展到现今阶段,大量日常、工作等事务产生的数据都已经信息化,人类产生的数据量相比以前有了爆炸式的增长,以前的传统的数据处理技术已经...

须臾之余
28分钟前
6
0
比特币从地址逆向计算私钥

区块链 区块链简介 说到比特币,就不得不提区块链。那什么是区块链呢? 区块链本质是一个数据集,只不过数据的组织采用了比较特殊的方式,就是把数据拆分为一块一块的小数据集。 为什么要进行...

trayvon
42分钟前
1
0
TypeScript……真香

写前端或者用 node 写命令行小工具一直采用的 es6 的语法,对于 TypeScript 则是秉持敬而远之的态度,毕竟团队中多推广一门语言所需要花费的精力都是让人望而却步的。所以对于 JavaScript 的...

郁也风
48分钟前
3
0
shell基本案例

1、自定义rm linux系统的rm命令太危险,一不小心就会删除掉系统文件。 写一个shell脚本来替换系统的rm命令,要求当删除一个文件或者目录时,都要做一个备份,然后再删除。下面分两种情况,做...

寰宇01
55分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部