尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

FFmpeg + qt 音频播放

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_swrContextch_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-6int 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. 创建并设置输出格式,缓冲区大小 创建并初始化设备IOQAudioOutput *m_audioOutput new QAudioOutput(m_audioFormat, this);// 44100Hz * 2ch * 2(字节) * 0.2sm_audioOutput-setBufferSize(static_castint 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);elsebreak;}}
返回列表