播放器开发(六):音频帧处理并用SDL播放

这篇具有很好参考价值的文章主要介绍了播放器开发(六):音频帧处理并用SDL播放。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】

步骤

AudioOutPut模块

1、初始化【分配缓存、读取信息】

2、开始线程工作【从队列读帧->重采样->SDL回调->写入音频播放数据->SDL进行播放】

主要代码

分配缓存

// 对于样本队列
av_audio_fifo_alloc(playSampleFmt, playChannels, spec.samples * 5);

// 对于帧的音频字节数据
// 首次计算帧大小,并且开辟缓冲区    
maxOutSamples = (int) av_rescale_rnd(decCtxSamples, playSampleRate, srcSampleRate, AV_ROUND_UP);
audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, maxOutSamples, playSampleFmt, 0);
audioBuffer = (uint8_t *) av_malloc(audioBufferSize);

重采样相关

//配置重采样器参数
swr_alloc_set_opts2(&swrContext,
                              &srcChannelLayout, playSampleFmt, playSampleRate,
                              &srcChannelLayout, AVSampleFormat(srcSampleFmt), srcSampleRate,
                              0, nullptr);
//初始化重采样器
swr_init(swrContext);

//重采样流程
// 计算重采样后要输出多少样本数
    delay = swr_get_delay(swrContext, sample_rate);
    out_samples = (int) av_rescale_rnd(
            nb_samples + delay,
            playSampleRate,
            sample_rate,
            AV_ROUND_DOWN);
    // 判断预测的输出样本数是否>本次任务的最大样本数
    if (out_samples > maxOutSamples) {
        // 释放缓冲区,重新初始化缓冲区大小
        av_freep(&audioBuffer);
        audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, out_samples, playSampleFmt, 0);
        audioBuffer = (uint8_t *) av_malloc(audioBufferSize);
        maxOutSamples = out_samples;
    }

    playSamples = swr_convert(swrContext, &audioBuffer, out_samples, (const uint8_t **) frame->data, nb_samples);

SDL的音频回调

// SDL音频回调函数提供了一个回调接口,可以让我们在音频设备需要数据的时候向里面写入数据
// 从而进行声音播放
// 回调函数示例 函数名自定义,放在类中需要加静态(static)
void AudioOutPut::AudioCallBackFunc(void *userdata, Uint8 *stream, int len) {
    //userdata 是在初始化时赋值的,有时候会把类中"this"传进去
    //stream 是音频流,在回调函数中需要把音频数据写入到stream就可以实现声音播放
    //len是由SDL传入的SDL缓冲区的大小,如果这个缓冲未满,我们就一直往里填充数据
    ...
}

完整模块

AudioOutPut

//AudioOutPut.h
#include "FFmpegHeader.h"
#include "SDL.h"
#include "queue/AVFrameQueue.h"
#include <QDebug>
#include <QObject>
#include <QtGui>
#include <QtWidgets>
#include <thread>

class AudioOutPut {
private:
    std::thread *m_thread;
    bool isStopped = true; // 是否已经停止 停止时退出线程
    bool isPlaying = false;// 是否正在播放
    bool isPause = false;  // 是否暂停

    void run();
    int resampleFrame(AVFrame *frame);


    int sdlCallBackMode = 1;
    QString url;            //视频地址
    uint8_t *audioBuffer;   //存储解码后音频buffer
    int audioBufferSize = 0;//buffer大小
    int audioBufferIndex = 0;
    SDL_mutex *mtx = nullptr;// 队列锁
    SDL_AudioDeviceID audioDevice;
    AVAudioFifo *fifo = nullptr;//Audio Buffer

    AVFrameQueue *frameQueue;    //解码后的帧队列
    SwrContext *swrContext;      //重采样上下文

    // 解码器上下文
    AVCodecContext *decCtx;          // 音频解码器上下文
    int srcChannels;                 // 源通道数
    AVChannelLayout srcChannelLayout;// 源通道布局
    enum AVSampleFormat srcSampleFmt;// 源采样格式
    int srcSampleRate;               // 源音频采样率

    // player
    int maxOutSamples; // 最大样本数,用于计算缓存区大小
    int playSamples;   // 最终播放的样本数
    int playSampleRate;// 最终播放的音频采样率
    enum AVSampleFormat playSampleFmt;
    int playChannels;// 源通道数
public:
    AudioOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue);
    int init(int mode = 1);
    static void AudioCallBackFunc(void *userdata, Uint8 *stream, int len);
    //SDL音频回调函数实体普通版
    void AudioCallBack(Uint8 *stream, int len);
    //SDL音频回调函数实体队列版
    void AudioCallBackFromQueue(Uint8 *stream, int len);
    int start();
};



//AudioOutPut.cpp
#include "AudioOutPut.h"
AudioOutPut::AudioOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue)
    : decCtx(dec_ctx), frameQueue(frame_queue) {
    srcSampleFmt = decCtx->sample_fmt;
    srcSampleRate = decCtx->sample_rate;
    srcChannelLayout = decCtx->ch_layout;
    srcChannels = srcChannelLayout.nb_channels;
}
int AudioOutPut::init(int mode) {
    sdlCallBackMode = mode;

    // SDL init
    if (SDL_Init(SDL_INIT_AUDIO) != 0) {
        qDebug() << "SDL_INIT_AUDIO error";
        return -1;
    }


    SDL_AudioSpec wanted_spec, spec;
    wanted_spec.channels = decCtx->ch_layout.nb_channels;
    wanted_spec.freq = decCtx->sample_rate;
    SDL_AudioFormat sample_type;
    switch (srcSampleFmt) {
        case AV_SAMPLE_FMT_FLTP:
        case AV_SAMPLE_FMT_FLT:
            sample_type = AUDIO_F32SYS;
            break;
        case AV_SAMPLE_FMT_U8P:
        case AV_SAMPLE_FMT_U8:
            sample_type = AUDIO_U8;
            break;
        case AV_SAMPLE_FMT_S64P:
        case AV_SAMPLE_FMT_S64:
        case AV_SAMPLE_FMT_S32P:
        case AV_SAMPLE_FMT_S32:
            sample_type = AUDIO_S32SYS;
            break;
        case AV_SAMPLE_FMT_S16P:
        case AV_SAMPLE_FMT_S16:
            sample_type = AUDIO_S16SYS;
            break;
        default:
            sample_type = AUDIO_S16SYS;
            qDebug() << "不支持的采样格式:AVSampleFormat(" << srcSampleFmt << ")";
    }

    wanted_spec.format = sample_type;
    wanted_spec.silence = 0;
    wanted_spec.callback = AudioCallBackFunc;
    wanted_spec.userdata = this;
    wanted_spec.samples = decCtx->frame_size;

    int ret;
    //    ret = SDL_OpenAudio(&wanted_spec, &spec);
    audioDevice = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, SDL_AUDIO_ALLOW_ANY_CHANGE);
    if (audioDevice == 0) {
        qDebug() << "SDL_OpenAudio error";
        return -1;
    }


    playChannels = spec.channels;
    playSampleRate = spec.freq;
    playSampleFmt = av_get_packed_sample_fmt(srcSampleFmt);


    if (mode == 1) {
        fifo = av_audio_fifo_alloc(playSampleFmt, playChannels, spec.samples * 5);
    }


    ret = swr_alloc_set_opts2(&swrContext,
                              &srcChannelLayout, playSampleFmt, playSampleRate,
                              &srcChannelLayout, AVSampleFormat(srcSampleFmt), srcSampleRate,
                              0, nullptr);
    if (ret != 0) {
        qDebug() << "swr_alloc_set_opts2错误";
        return -1;
    }
    if (!swrContext) {
        qDebug() << "创建音频重采样上下文错误 swr_alloc";
        return -1;
    }
    ret = swr_init(swrContext);
    if (ret < 0) {
        qDebug() << "初始化音频重采样上下文错误 swr_init";
        return -1;
    }

    // 解码器上下文保存的帧样本数
    int decCtxSamples = 1024;
    if (decCtx->frame_size > 1024) {
        decCtxSamples = decCtx->frame_size;
    }

    // 首次计算帧大小,并且开辟缓冲区
    maxOutSamples = (int) av_rescale_rnd(decCtxSamples, playSampleRate, srcSampleRate, AV_ROUND_UP);
    audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, maxOutSamples, playSampleFmt, 0);
    audioBuffer = (uint8_t *) av_malloc(audioBufferSize);
    return 1;
}

void AudioOutPut::AudioCallBackFunc(void *userdata, Uint8 *stream, int len) {
    AudioOutPut *player = (AudioOutPut *) userdata;
    if (player->sdlCallBackMode == 1) {
        player->AudioCallBackFromQueue(stream, len);
    } else {
        player->AudioCallBack(stream, len);
    }
}

void AudioOutPut::AudioCallBack(Uint8 *stream, int len) {
    int len1;// sdl的内部stream可用空间
    /*   len是由SDL传入的SDL缓冲区的大小,如果这个缓冲未满,我们就一直往里填充数据 */
    while (len > 0) {
        /*  audioBufferIndex 和 audioBufferSize 标示我们自己用来放置解码出来的数据的缓冲区,*/
        /*   这些数据待copy到SDL缓冲区, 当audioBufferIndex >= audioBufferSize的时候意味着我*/
        /*   们的缓冲为空,没有数据可供copy,这时候需要调用audio_decode_frame来解码出更多的桢数据 */
        if (audioBufferIndex >= audioBufferSize) {
            AVFrame *frame = frameQueue->pop(10);
            if (frame) {
                audioBufferSize = resampleFrame(frame);
                /* audioBufferSize < 0 标示没能解码出数据,我们默认播放静音 */
                if (audioBufferSize <= 0) {
                    /* silence */
                    audioBufferSize = 1024;
                    /* 清零,静音 */
                    memset(audioBuffer, 0, audioBufferSize);
                }
            }
            audioBufferIndex = 0;
        }

        /*  当audioBufferIndex < audioBufferSize 查看stream可用空间,决定一次copy多少数据,剩下的下次继续copy */
        len1 = audioBufferSize - audioBufferIndex;

        // 可用空间>
        if (len1 > len) {
            len1 = len;
        }

        if (audioBuffer == nullptr) return;
        memcpy(stream, (uint8_t *) audioBuffer + audioBufferIndex, len1);
        len -= len1;
        stream += len1;
        audioBufferIndex += len1;
    }
}

void AudioOutPut::AudioCallBackFromQueue(Uint8 *stream, int len) {
    //由于AVAudioFifo非线程安全,且是子线程触发此回调,所以需要加锁
    SDL_LockMutex(mtx);
    //读取队列中的音频数据
    av_audio_fifo_read(fifo, (void **) &stream, playSamples);
    SDL_UnlockMutex(mtx);
}
int AudioOutPut::start() {
    SDL_PauseAudioDevice(audioDevice, 0);
    //    SDL_PauseAudio(0);

    if (sdlCallBackMode == 1) {
        m_thread = new std::thread(&AudioOutPut::run, this);
        if (!m_thread->joinable()) {
            qDebug() << "AudioOutPut音频帧处理线程创建失败";
            return -1;
        }
    }
    isStopped = false;
    isPlaying = true;
    return 0;
}
void AudioOutPut::run() {
    AVFrame *frame;
    while (!isStopped) {
        frame = frameQueue->pop(10);
        if (frame) {
            audioBufferSize = resampleFrame(frame);
            while (true) {
                SDL_LockMutex(mtx);
                if (av_audio_fifo_space(fifo) >= playSamples) {
                    av_audio_fifo_write(fifo, (void **) &audioBuffer, playSamples);
                    SDL_UnlockMutex(mtx);
                    av_frame_unref(frame);
                    break;
                }
                SDL_UnlockMutex(mtx);
                //队列可用空间不足则延时等待
                SDL_Delay((double) playSamples / playSampleRate);
            }
        }
    }
}
int AudioOutPut::resampleFrame(AVFrame *frame) {
    int64_t delay;  // 重采样后延迟
    int out_samples;// 预测的重采样后的输出样本数
    int sample_rate;// 帧原采样率
    int nb_samples; // 帧原样本数
    sample_rate = frame->sample_rate;
    nb_samples = frame->nb_samples;

    // 计算重采样后要输出多少样本数
    delay = swr_get_delay(swrContext, sample_rate);
    out_samples = (int) av_rescale_rnd(
            nb_samples + delay,
            playSampleRate,
            sample_rate,
            AV_ROUND_DOWN);
    // 判断预测的输出样本数是否>本次任务的最大样本数
    if (out_samples > maxOutSamples) {
        // 释放缓冲区,重新初始化缓冲区大小
        av_freep(&audioBuffer);
        audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, out_samples, playSampleFmt, 0);
        audioBuffer = (uint8_t *) av_malloc(audioBufferSize);
        maxOutSamples = out_samples;
    }

    playSamples = swr_convert(swrContext, &audioBuffer, out_samples, (const uint8_t **) frame->data, nb_samples);
    if (playSamples <= 0) {
        return -1;
    }
    return av_samples_get_buffer_size(nullptr, srcChannels, playSamples, playSampleFmt, 1);
}

PlayerMain

添加音频输出代码


AudioOutPut *audioOutPut;
audioOutPut = new AudioOutPut(audioDecodeThread->dec_ctx, &audioFrameQueue);
audioOutPut->init(1);
audioOutPut->start();

测试运行结果

如果需要同时执行视频和音频的输出,记得要在解复用模块那把限制队列大小的位置把视频队列的大小限制给去掉。


目前只是实现了音频播放和视频渲染显示画面,但是可以看到音频和视频是不同步的,下一章我们就要让音频和视频同步起来。

播放器开发(六):音频帧处理并用SDL播放结果文章来源地址https://www.toymoban.com/news/detail-830652.html

到了这里,关于播放器开发(六):音频帧处理并用SDL播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于 FFmpeg 的跨平台视频播放器简明教程(五):使用 SDL 播放视频

    基于 FFmpeg 的跨平台视频播放器简明教程(一):FFMPEG + Conan 环境集成 基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux) 基于 FFmpeg 的跨平台视频播放器简明教程(三):视频解码 基于 FFmpeg 的跨平台视频播放器简明教程(四):像素格式与格式转换

    2024年02月12日
    浏览(62)
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(三)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月05日
    浏览(65)
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(二十一)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月02日
    浏览(69)
  • Android 中封装优雅的 MediaPlayer 音频播放器,支持多个播放器

    Android 中封装优雅的 MediaPlayer 音频播放器,支持多个播放器实例的示例: 上述代码中,使用 getInstance() 方法获取 AudioPlayer 的单例对象,参数传入 Context 对象。 在 getInstance() 方法中判断单例对象是否为空,如果为空则创建新的 AudioPlayer 对象,否则返回已有的单例对象。 这样

    2024年02月12日
    浏览(44)
  • uniapp之音频播放器

    日常业务会遇到 微信音频 mp3播放器, 特别是微信文章阅读,下面仅作参考 1.解决滑动卡顿bug 加了防抖 2.滑动进度条时 先暂停再播放 就不会出现卡顿 3.初始化时 要onCanplay钩子中 setInterval 获取音频文件长度 不然会显示 0 注意用了vantUI 框架的icon 不用可以去掉 换图片或者其他

    2024年02月11日
    浏览(41)
  • Audio API 实现音频播放器

    市面上实现音频播放器的库有很多,比如wavesurfer.js、howler.js等等,但是都不支持大音频文件处理,100多M的文件就有可能导致程序崩溃。总之和我目前的需求不太符合,所以打算自己实现一个音频播放器,这样不管什么需求 在技术上都可控。下面我们简单介绍下 wavesurferJs 、

    2024年02月10日
    浏览(42)
  • Python实现本地视频/音频播放器

    在Python中,有几个库可以用于视频播放,但是没有一个库是完美的,因为它们可能依赖于外部软件或有一些限制。 先看介绍用Python实现本地视频播放器,再介绍用Python实现本地音乐播放器。 Python 实现本地视频播放器 与HTML5+JavaScript实现本地视频播放器相比,使用Python实现比

    2024年04月26日
    浏览(36)
  • Vue实现自定义音频播放器组件

    template javascript less 文档参考 关于 Audio 自定义样式 H5 audio 音频标签自定义样式修改以及添加播放控制事件

    2024年02月12日
    浏览(33)
  • 用selenium爬取直播信息,前端音频播放器

    #保存数据的函数 def save_data(self,data_list,i): #在当前目录下将数据存为txt文件 with open(‘./douyu.txt’,‘w’,encoding=‘utf-8’) as fp: for data in data_list: data = str(data) fp.write(data+‘n’) print(“第%d页保存完成!” % i) (2)保存为json文件 #保存数据的函数 def save_data(self,data_list,i): with op

    2024年04月16日
    浏览(44)
  • 音频播放器Web页面代码实例(基于HTML5)

    音频播放器Web页面代码实例(基于HTML5):   特别需要注意的点:     如果上传文件时设置的是默认转码方式,所有的文件都会转码为视频文件,使用音频播放器播放视频文件时,只会播放声音,没有图像。     如果上传文件时设置了\\\"源文件播放\\\",平台不会对源文件进行

    2024年02月16日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包