ffmpeg进行音频解码,QAudioOutput播放解码后的音频

项目在此 https://github.com/qyvlik/AudioTestByFFmpeg

首先我们按照Qt给的官方例子初始化一个QAudioFormat,设置好他的各项参数,ffmpeg解码后的音频可以考虑使用如下的参数进行播放

    format.setSampleRate(44100);
    format.setChannelCount(2);
    format.setCodec("audio/pcm");
    format.setSampleType(QAudioFormat::SignedInt);
    format.setSampleSize(16);
    format.setByteOrder(QAudioFormat::LittleEndian);

然后接下来使用 QAudioDeviceInfo 查看本机的音频设备(声卡)。如果音频设备有问题的话,就返回-1

    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
    if (!info.isFormatSupported(format)) {
        qDebug() << "Raw audio format not supported by backend, cannot play audio.";
        return -1;
    }

如果声卡设备没有问题,就使用前面设置好参数的QAudioFormat 对象来构造一个 QAudioOutput对象 ,并取出QAudioOutput对象 内部的QIODevice 对象。向这个QIODevice对象写入数据,QAudioOutput对象 便会播放出声音。

    QAudioOutput *audio;
    audio = new QAudioOutput(format,&a);

    QIODevice *out = audio->start();

接下来进入主要由 ffmpeg 代码构成的音频解码与播放函数,此函数参照 此链接

int decodeAndPlay(const char *filename,QIODevice *out)
{
    //![url] http://blog.chinaunix.net/uid-23043718-id-2563087.html

    av_register_all();//注册所有可解码类型
    AVFormatContext *pInFmtCtx=NULL;//文件格式
    AVCodecContext *pInCodecCtx=NULL;//编码格式

    if (av_open_input_file(&pInFmtCtx,filename,NULL, 0, NULL)!=0)//获取文件格式
        printf("av_open_input_file error\n");
    if(av_find_stream_info(pInFmtCtx) < 0)//获取文件内音视频流的信息
        printf("av_find_stream_info error\n");


    //! Find the first audio stream
    unsigned int j;
    int    audioStream = -1;
    //找到音频对应的stream
    for(j=0; j<pInFmtCtx->nb_streams; j++){
        if(pInFmtCtx->streams[j]->codec->codec_type==CODEC_TYPE_AUDIO){
            audioStream=j;
            break;
        }
    }
    if(audioStream==-1) {
        printf("input file has no audio stream\n");
        return 0; // Didn't find a audio stream
    }
    printf("audio stream num: %d\n",audioStream);

    //! Decode Audio
    pInCodecCtx = pInFmtCtx->streams[audioStream]->codec;//音频的编码上下文
    AVCodec *pInCodec=NULL;
    pInCodec = avcodec_find_decoder(pInCodecCtx->codec_id);//根据编码ID找到用于解码的结构体

    if(pInCodec==NULL) {
        printf("error no Codec found\n");
        return -1 ; // Codec not found
    }


    if(avcodec_open(pInCodecCtx, pInCodec)<0) {
        printf("error avcodec_open failed.\n");
        return -1; // Could not open codec
    }

    /*static*/ AVPacket packet;

    int rate = pInCodecCtx->bit_rate;

    printf(" bit_rate = %d \r\n", pInCodecCtx->bit_rate);
    printf(" sample_rate = %d \r\n", pInCodecCtx->sample_rate);
    printf(" channels = %d \r\n", pInCodecCtx->channels);
    printf(" code_name = %s \r\n", pInCodecCtx->codec->name);
    printf(" block_align = %d\n",pInCodecCtx->block_align);

    //

    uint8_t *pktdata;
    int pktsize;

    // 1 second of 48khz 32bit audio 192000
    int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;

    uint8_t * inbuf = (uint8_t *)malloc(out_size);

    long start = clock();

    // pInFmtCtx 中调用对应格式的packet获取函数

    const double K=0.0054;
    double sleep_time=0;


    while(av_read_frame(pInFmtCtx, &packet)>=0) {

        //如果是音频
        if(packet.stream_index==audioStream) {
            pktdata = packet.data;
            pktsize = packet.size;

            while(pktsize>0){
                out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;
                //解码
                int len = avcodec_decode_audio2(pInCodecCtx,(short*)inbuf,&out_size,pktdata,pktsize);
                if (len<0) {
                    printf("Error while decoding.\n");
                    break;
                }
                if(out_size>0) {

                    // out_size sleep_time
                    // 16384            91
                    //  8096            46
                    //  4608            25
                    // y = k*x + b;
                    // sleep_time = 0.0054 * out_size + b;

                    //      rate     b
                    //    320000     1.9
                    //    128000     0.8
                    //
                    //    1411200      0

                    if(rate >= 1411200){
                        sleep_time = K * out_size - 1;
                        //printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
                    }else if(rate >= 320000 ){
                        sleep_time = K * out_size + 1.9;
                        //printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
                    } else if(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);
                    }

                    out->write((char*)inbuf,out_size);

                    QTest::qSleep( sleep_time );
                }
                pktsize -= len;
                pktdata += len;
            }
        }
        av_free_packet(&packet);
    }

    long end = clock();
    printf("cost time :%f\n",double(end-start)/(double)CLOCKS_PER_SEC);

    free(inbuf);

    if (pInCodecCtx!=NULL) {
        avcodec_close(pInCodecCtx);
    }

    av_close_input_file(pInFmtCtx);

    printf("good bye !\n");

    return 0;
}


这里 ffmpeg 代码稍作简介

1.注册所有可解码类型

av_register_all();


2.获取文件格式

if (av_open_input_file(&pInFmtCtx,filename,NULL, 0, NULL)!=0)//获取文件格式
        printf("av_open_input_file error\n");

3. 获取文件内音视频流的信息
    if(av_find_stream_info(pInFmtCtx) < 0)//获取文件内音视频流的信息
        printf("av_find_stream_info error\n");

4. 找到音频流
    //! Find the first audio stream
    unsigned int j;
    int    audioStream = -1;
    //找到音频对应的stream
    for(j=0; j<pInFmtCtx->nb_streams; j++){
        if(pInFmtCtx->streams[j]->codec->codec_type==CODEC_TYPE_AUDIO){
            audioStream=j;
            break;
        }
    }
    if(audioStream==-1) {
        printf("input file has no audio stream\n");
        return 0; // Didn't find a audio stream
    }
    printf("audio stream num: %d\n",audioStream);


5.获取音频编码上下文,获取解码器,然后打印音频编码信息

    pInCodecCtx = pInFmtCtx->streams[audioStream]->codec;//音频的编码上下文
    AVCodec *pInCodec=NULL;
    pInCodec = avcodec_find_decoder(pInCodecCtx->codec_id);//根据编码ID找到用于解码的结构体

    if(pInCodec==NULL) {
        printf("error no Codec found\n");
        return -1 ; // Codec not found
    }


    if(avcodec_open(pInCodecCtx, pInCodec)<0) {
        printf("error avcodec_open failed.\n");
        return -1; // Could not open codec
    }

    /*static*/ AVPacket packet;

    int rate = pInCodecCtx->bit_rate;

    printf(" bit_rate = %d \r\n", pInCodecCtx->bit_rate);
    printf(" sample_rate = %d \r\n", pInCodecCtx->sample_rate);
    printf(" channels = %d \r\n", pInCodecCtx->channels);
    printf(" code_name = %s \r\n", pInCodecCtx->codec->name);
    printf(" block_align = %d\n",pInCodecCtx->block_align);

6.解码开始,由于是将解码的数据直接写入QAudioOutput的IO设备中去,如果写入速度太快,会导致音频播放速度快,故,每次写入数据后,都会根据音频编码的信息计算出休眠的时间,让 QAudioOutput 对象有足够的时间去播放声音

    uint8_t *pktdata;
    int pktsize;

    // 1 second of 48khz 32bit audio 192000
    int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;

    uint8_t * inbuf = (uint8_t *)malloc(out_size);

    long start = clock();

    // pInFmtCtx 中调用对应格式的packet获取函数

    const double K=0.0054;
    double sleep_time=0;

    //! Decode Audio
    while(av_read_frame(pInFmtCtx, &packet)>=0) {

        //如果是音频
        if(packet.stream_index==audioStream) {
            pktdata = packet.data;
            pktsize = packet.size;

            while(pktsize>0){
                out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;
                //解码
                int len = avcodec_decode_audio2(pInCodecCtx,(short*)inbuf,&out_size,pktdata,pktsize);
                if (len<0) {
                    printf("Error while decoding.\n");
                    break;
                }
                if(out_size>0) {

                    // out_size sleep_time
                    // 16384            91
                    //  8096            46
                    //  4608            25
                    // y = k*x + b;
                    // sleep_time = 0.0054 * out_size + b;

                    //      rate     b
                    //    320000     1.9
                    //    128000     0.8
                    //
                    //    1411200      0

                    if(rate >= 1411200){
                        sleep_time = K * out_size - 1;
                        //printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
                    }else if(rate >= 320000 ){            // rate 在320000 的音乐播放最好,时间误差 5分钟 +- 1 秒
                        sleep_time = K * out_size + 1.9;
                        //printf(" rate : %d,sleep : %lf \n",rate,sleep_time);
                    } else if(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);
                    }

                    out->write((char*)inbuf,out_size); // 这里正是向音频设备写入数据

                    QTest::qSleep( sleep_time );      // 写入数据后,等音频设备播放
                }
                pktsize -= len;
                pktdata += len;
            }
        }
        av_free_packet(&packet);
    }

    long end = clock();
    printf("cost time :%f\n",double(end-start)/(double)CLOCKS_PER_SEC);

7.后期,释放掉申请的ffmpeg的内存

 free(inbuf);

    if (pInCodecCtx!=NULL) {
        avcodec_close(pInCodecCtx);
    }

    av_close_input_file(pInFmtCtx);

    printf("good bye !\n");

    return 0;

本程序的思路如上所述,总逃不了声音播放噪音的问题(音频采样率高的问题不大),在下一个解决方案中会尝试使用线程来解码播放。

由于ffmpeg只负责解码音频,并不负责任对音频的加工处理,所以声音听起来有点不同。

代码&可执行程序下载

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
要把音频解码后的AVFrame保存为PCM格式的音频文件,你可以使用FFmpeg提供的API进行处理。具体步骤如下: 1. 打开音频文件并获取音频流信息: ``` AVFormatContext *formatCtx = avformat_alloc_context(); if (avformat_open_input(&formatCtx, inputPath, NULL, NULL) != 0) { // 打开音频文件失败 return; } if (avformat_find_stream_info(formatCtx, NULL) < 0) { // 获取音频流信息失败 return; } int audioStreamIndex = -1; for (int i = 0; i < formatCtx->nb_streams; i++) { if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audioStreamIndex = i; break; } } if (audioStreamIndex == -1) { // 找不到音频流 return; } AVCodecParameters *audioCodecParams = formatCtx->streams[audioStreamIndex]->codecpar; ``` 2. 获取音频解码器并打开解码器: ``` AVCodec *audioCodec = avcodec_find_decoder(audioCodecParams->codec_id); AVCodecContext *audioCodecCtx = avcodec_alloc_context3(audioCodec); avcodec_parameters_to_context(audioCodecCtx, audioCodecParams); if (avcodec_open2(audioCodecCtx, audioCodec, NULL) < 0) { // 打开音频解码器失败 return; } ``` 3. 循环读取音频帧并解码: ``` AVPacket pkt; av_init_packet(&pkt); AVFrame *frame = av_frame_alloc(); while (av_read_frame(formatCtx, &pkt) >= 0) { if (pkt.stream_index == audioStreamIndex) { int ret = avcodec_send_packet(audioCodecCtx, &pkt); if (ret < 0) { // 发送音频数据包到解码器失败 break; } while (ret >= 0) { ret = avcodec_receive_frame(audioCodecCtx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // 无法从解码器中接收更多音频帧 break; } else if (ret < 0) { // 解码音频帧失败 break; } // 将音频帧保存为PCM格式的文件 // 这里省略将AVFrame保存为PCM格式文件的代码 } } av_packet_unref(&pkt); } av_frame_free(&frame); ``` 4. 关闭解码器和音频文件: ``` avcodec_free_context(&audioCodecCtx); avformat_close_input(&formatCtx); ``` 在将AVFrame保存为PCM格式文件时,你需要将AVFrame中的音频数据读取出来,并将其保存为PCM格式的文件。具体的方法可以参考FFmpeg的文档和示例代码。另外,需要注意的是,在将音频帧保存为PCM格式的文件时,你还需要指定音频的采样率、采样格式、声道布局等参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值