≮傷£≯√头像
关注

FFmpeg + qt 音频播放

1. 读取 文件名

avformat_open_input(&fmt_ctx, filePath.toStdString().c_str(),nullptr, nullptr);

2. 读取完整文件流信息

avformat_find_stream_info(fmt_ctx,nullptr);

3. 获取音频流索引

int audioStreamIndex = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, 0 ,nullptr);

4. 获取音频流结构体

AVStream m_audioStream = fmt_ctx->streams[audioStreamIndex];

5. 获取解码器静态指针

const AVCodec* codec = avcodec_find_decoder(fmt_ctx->codecper->codec_id);

6. 获取解码器上下文

decoder_ctx =  avcodec_alloc_context3(codec);

7. 复制解码参数

avcodec_parameters_to_context(decoder_ctx, m_audioStream->codecper);

8. 打开解码器

avcodec_open2(decoder_ctx,codec, nullptr);

9. 创建音频帧

AVFrame m_audioFrame = av_frame_alloc();

10. 创建并初始化音频转换器

SwrContext *m_swrContext;

AVChannelLayout *ch_layout;

//int av_channel_layout_from_mask(AVChannelLayout *ch_layout, uint64_t mask);

av_channel_layout_form_mask(ch_layout, AV_CH_LAYOUT_STEREO);

swr_alloc_set_opts2(&m_swrContext

        ch_layout, AV_SAMPLE_S16,44100,

        &decoder_ctx->ch_layout,

        deocder_ctx->sample_fmt,

        decoder_ctx->sample_rate,

        0, nullptr   //日志偏移量  自定义日志上下文

);

swr_init(m_swrContext);

11. 创建音频缓冲区(根据输入的采样率和样本数, 获取输出的样本数以及可能积压的延迟样本数计算做大输出样本数,根据最大输出样本数和采样率计算缓冲区大小,申请缓冲区大小的内存)

int inRate = decoder_ctx->sample_rate;

int inSamples = decoder_ctx->frame_size > 0 ? decoder_ctx->frame_size : 1024;

int outSamples = av_rescle_rnd(inSamples, 44100, inRate, AV_ROUND) *2;

// 按两秒估算

int delaySample = av_rescale_rnd(2 * inRate, 44100, inRate, AV_ROUND);

int maxOutSamples = outSamples + delaySample; 

int bufferSize = av_sample_get_buffer_size(nullptr, 2, maxOutSamples, AV_SAMPLE_S16, 0);

uint8_t *m_audioBuffer = (uint8_t*)av_malloc(bufferSize);

12. 创建压缩包

AVPacket* m_packet = av_packet_alloc();

13. 循环解码 (1. 从fmt_ctx读取完整数据包  2. 将数据包发送到解码器  3. 从解码器中读取解码后的音频帧 4. 计算需要输出的样本数,包括swr内延迟的 5. 重采样 6. 将数据发送到主线程)

while(!isInterruptionRequested()){

        av_read_frame(fmt_ctx, m_packet);

        avcodec_send_packet(decoder_ctx, m_packet);

        while(avcodec_receive_frame(decoder_ctx, m_audioFrame) >= 0){

               int delay = swr_get_delay(m_swrContext, decoder_ctx->sample_rate);

                int outSample = av_rescale_rnd(delay + m_audioFrame->nb_samples, 44100,                         m_audioFrame->sample_rate, AV_ROUND_UP);

                int conver = swr_conver(m_swrContext, &m_audioBuffer, outSample,

                        (const uint8_t **)m_audioFrame->data, m_audioFrame->nb_samples

                );

                if(conver > 0){

                        int outSize = conver * 2 * sizeof(int16_t);

                        QByteArray pcm((const char*)m_audioBuffer, outSize);

                        emit sendPcm(pcm);

                }

                av_frame_unref(m_audioFrame);

        }

        av_packet_unref(&m_packet)

}

14. 读取解码器中残留的帧(13上述中的 3-6)

int ret = 0;

while(ret >= 0){

        ret = av_read_frame(decoder_ctx, m_audioFrame);

        if(ret == AVERROR_EOF || ret == AVERROR(EAGAIN))

                break;

        int delay = swr_get_delay(m_swrContext, m_audioFrame->nb_samples);

        int outSamples = av_rescale_rnd(delay + m_audioFrame->nb_samples, 44100,                 m_audioFrame->sample_rate, AV_ROUND_UP);

        int conver = swr_conver(&m_swrContext,

                m_audioBuffer, outSamples,

                (const uint8_t**)m_audioFrame->data,

                m_audioFrame->nb_samples

        ); 

        if(conver > 0){

                int outSize = conver *2 * sizeof(int16_t);

                QByteArray pcm((const char*)m_audioBuffer, outSize);

                emit sendPcm(pcm);

        }

        av_frame_unref(m_audioFrame);

}

av_packet_unref(&m_packet);

15. 释放

void release(){

        if(m_swrContext){

                swr_free(&m_swrContext);

                m_swrContext = nullptr;

        }

        if(m_audioFrame){

                av_frame_free(&m_audioFrame);

                m_audioFrame = nullptr;

        }

        if(m_audioBuffer){

                av_freep(&m_audioBuffer);

                m_audioBuffer = nullptr;

        }

        if(decoder_ctx){

                avcodec_free_context(&decoder_ctx);

                decoder_ctx = nullptr;

        }

        if(fmt_ctx){

                avformat_free_context(&fmt_ctx);

                fmt_ctx = nullptr;

        }

}

主线程

1. 设置音频格式

QAudioFormat m_audioFormat;

m_audioFormat.setSampleRate(44100);
m_audioFormat.setChannelCount(2);

m_audioFormat.setSampleSize(16);

m_audioFormat.setSampleType(QAudioSample::SignedInt);

m_audioFormat.setByteOrder(QAudioSample::LittleEndian);

2. 设置音频播放设备

QAudioDeviceInfo m_audioDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();

3. 检查设备是否支持,不支持就用最接近的格式

if(!m_audioDeviceInfo.isFormatSupported(m_audioFormat)){

        qDebug() << "格式不支持,使用最接近的格式";

        m_audioFormat = m_audioDeviceInfo.nearestFormat(m_audioForamt);

        qDeubg() << "实际支持格式:" << m_audioFormat.sampleRate() << "Hz"

        << m_audioFormat.sampleSize()  << "bit" << m_audioFormat_channelCount() << "ch";

}

4. 创建并设置输出格式,缓冲区大小   创建并初始化设备IO

QAudioOutput *m_audioOutput =  new QAudioOutput(m_audioFormat, this);

// 44100Hz * 2ch * 2(字节) * 0.2s

m_audioOutput->setBufferSize(static_cast<int> 44100 *2 *2 *0.2); 

QIODevice m_audioIODevice = m_audioOutput->start();

5. 接收pcm 并加入输出设备

connect(m_audioPlay, &AudioPlay, this, &MainWindow::playAudio);

m_audioPlay->start();

void playAudio(QByteArray pcm){

       if(pcm.isEmpty())

                return;

        QMutexLocker locker(&m_pcmMutex);

        m_pcmQueue.append(pcm);

}

6. 定时器:每10ms从队列取数据写入设备

m_feedTimer = new QTimer(this);

connect(m_feedTimer, &QTimer::timeout, this, &MainWindow::feedAudio);

m_feedTimer->start(10);

void feedAudio(){

        if(!m_audioIODevice || !m_audioOutput)

                return;

        QMutextLocker locker(&m_pcmMutext);

        while(!m_pcmQueue.empty()){

                // 获取音频设备缓冲区当前剩余可写入字节数

                int free = m_audioOutput.bytesFree();

                if(free < 0) break;

                // 获取 剩余字节数 和 缓冲队列剩余字节数 中的最小值

                int minBytes = qMin(free, m_pcmQueue.size());

                int written = m_audioIODevice->write(m_pcmQueue.constData, minBytes);

               if(written > 0)

                        // 将写入的字节从缓冲队列移除

                        m_pcmQueue.remove(0, minBytes);

                else

                        break;

        }

}

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_59167692/article/details/163714290

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--