ffmpeg播放器(二)音频解码与播放

2023-05-16

音频解码和播放的前面准备工作和视频的格式差不多,创建两个线程分别解码和播放,这里统一只放代码了。

void AudioChannel::play() {
    //设置为播放状态
    packets.setWork(1);
    frames.setWork(1);

    isPlaying = 1;
    //1 、解码
    pthread_create(&pid_audio_decode, 0, audio_decode, this);
    //2、 播放
    pthread_create(&pid_audio_play, 0, audio_play, this);
}

 //声明并且实现
void *audio_decode(void *args) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
    audioChannel->decode();
    return 0;
}

void *audio_play(void *args) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
    audioChannel->_play();
    return 0;
}

解码和视频完全一样:

void AudioChannel::decode() {
    AVPacket *packet = 0;
    while (isPlaying) {
        //取出一个数据包
        int ret = packets.pop(packet);
        if (!isPlaying) {
            break;
        }
        //取出失败
        if (!ret) {
            continue;
        }
        //把包丢给解码器
        ret = avcodec_send_packet(avCodecContext, packet);
        releaseAvPacket(&packet);
        //重试
        if (ret != 0) {
            break;
        }
        //代表了一个图像 (将这个图像先输出来)
        AVFrame *frame = av_frame_alloc();
        //从解码器中读取 解码后的数据包 AVFrame
        ret = avcodec_receive_frame(avCodecContext, frame);
        //需要更多的数据才能够进行解码
        if (ret == AVERROR(EAGAIN)) {
            continue;
        } else if (ret != 0) {
            break;
        }
        //再开一个线程 来播放 (流畅度)
        frames.push(frame);
    }
    releaseAvPacket(&packet);
};

OpenSL ES的开发流程主要有如下7个步骤:

1. 创建接口对象

创建引擎与接口

/**
     * 1、创建引擎并获取引擎接口
     */
    SLresult result;
    // 1.1 创建引擎 SLObjectItf engineObject
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 1.2 初始化引擎  init
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 1.3 获取引擎接口SLEngineItf engineInterface
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
                                           &engineInterface);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

2. 设置混音器

/**
     * 2、设置混音器
     */
    // 2.1 创建混音器SLObjectItf outputMixObject
    result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0,
                                                 0, 0);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 2.2 初始化混音器outputMixObject
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

3. 创建播放器

/**
    * 2、设置混音器
    */
    // 2.1 创建混音器SLObjectItf outputMixObject
    result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0,
                                                 0, 0);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 2.2 初始化混音器outputMixObject
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

4. 设置播放回调函数

 /**
        * 3、创建播放器
        */
    //3.1 配置输入声音信息
    //创建buffer缓冲类型的队列 2个队列
    SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                            2};
    //pcm数据格式
    //pcm+2(双声道)+44100(采样率)+ 16(采样位)+16(数据的大小)+LEFT|RIGHT(双声道)+小端数据
    SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16,
                            SL_PCMSAMPLEFORMAT_FIXED_16,
                            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
                            SL_BYTEORDER_LITTLEENDIAN};

    //数据源 将上述配置信息放到这个数据源中
    SLDataSource slDataSource = {&android_queue, &pcm};

    //3.2  配置音轨(输出)
    //设置混音器
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};
    //需要的接口  操作队列的接口
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    //3.3 创建播放器
    (*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &slDataSource,
                                          &audioSnk, 1,
                                          ids, req);
    //初始化播放器
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);

    //得到接口后调用  获取Player接口
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);

5. 设置播放状态

 /**
       * 4、设置播放回调函数
       */
    //获取播放器队列接口
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                    &bqPlayerBufferQueueInterface);
    //设置回调
    (*bqPlayerBufferQueueInterface)->RegisterCallback(bqPlayerBufferQueueInterface,
                                                      bqPlayerCallback, this);

6. 启动回调函数

bqPlayerCallback(bqPlayerBufferQueueInterface, this);

获取pcm数据

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(context);
    //获得pcm 数据 多少个字节 data
    int dataSize = audioChannel->getPcm();
    if(dataSize > 0 ){
        // 接收16位数据
        (*bq)->Enqueue(bq,audioChannel->data,dataSize);
    }


//返回获取的pcm数据大小
int AudioChannel::getPcm() {
    int data_size = 0;
    AVFrame *frame;
    int ret = frames.pop(frame);
    if (!isPlaying) {
        if (ret) {
            releaseAvFrame(&frame);
        }
        return data_size;
    }
    //48000HZ 8位 =》 44100 16位
    //重采样
    // 假设我们输入了10个数据 ,swrContext转码器 这一次处理了8个数据
    // 那么如果不加delays(上次没处理完的数据) , 积压
    int64_t delays = swr_get_delay(swrContext,frame->sample_rate);
    // 将 nb_samples 个数据 由 sample_rate采样率转成 44100 后 返回多少个数据
    // 10  个 48000 = nb 个 44100
    // AV_ROUND_UP : 向上取整 1.1 = 2
    int64_t max_samples =  av_rescale_rnd(delays+frame->nb_samples,out_sample_rate,frame->sample_rate,AV_ROUND_UP);
    //上下文+输出缓冲区+输出缓冲区能接受的最大数据量+输入数据+输入数据个数
    //返回 每一个声道的输出数据
    int samples = swr_convert(swrContext, &data, max_samples, (const uint8_t **)frame->data, frame->nb_samples); //转数据存放在data里面
    //获得   samples 个   * 2 声道 * 2字节(16位)
    data_size =  samples * out_samplesize * out_channels ; //字节大小
    //获取 frame 的一个相对播放时间(相对开始播放)
    //获得 相对播放这一段数据的秒数
    clock=frame->pts* av_q2d(time_base);
    return data_size;
}

音频项目基本实现。

后面将进行音频与视频的同步和找泄露等问题。

链接:https://pan.baidu.com/s/123di0cljvj0TatMeKwaHEA 
提取码:z0d2 
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ffmpeg播放器(二)音频解码与播放 的相关文章

随机推荐

  • zeal 文档下载及其离线文档下载

    下载地址 百度网盘 链接 xff1a https pan baidu com s 1r4d1OfrUTrbaZ9k9YfdWLA pwd 61 o7au 提取码 xff1a o7au 下载讲解 xff1a 1 下载安装 2 Tools gt
  • Linux系统部署JAVA项目

    Linux系统部署项目 一 准备工作 1 WinSCP 远程服务器可视化工具 2 Xshell5 linux服务器打命令用的 3 准备一台阿里云 华为云 腾讯云服务器 要求 xff1a 系统镜像centeOS7 0以上 二 开始搭建环境 1
  • Spring Boot 之---什么是热部署?---怎么使用?

    Spring Boot 之 什么是热部署 xff1f 怎么使用 xff1f 1 什么是热部署2 怎么启用热部署 xff08 1 xff09 手动配置 xff08 2 xff09 自动配置 总结 1 什么是热部署 所谓热部署 xff0c 就是
  • 什么是反向代理?

    正向代理 当客户端访问一台服务器有障碍 xff0c 访问不到的时候 xff0c 这时候就可以找一台可以访问到该服务器的另外一台服务器去代替他去访问 xff0c 这台代替他去访问的服务器称之为代理服务器 然后客户端就可以把请求发送给代理服务器
  • Java IO流 实现文件复制

    Java IO流 实现文件复制 思路 1 将指定文件转换成输入流 2 创建数组接收输入字节 3 将文件复制想要存放的轮径创建输出流 4 将输入流的字节输出出去 实现 span class token comment 创建输入和输出流 spa
  • Python之文件读写

    1 写文件 f 61 open 39 out txt 39 39 w 39 f write 39 s d d d d 0 0 0 0 0 0 0 39 bbx name bbx x bbx y bbx w bbx h f close 2 读
  • Java 基于TCP的socket实现文件传输

    Java 基于TCP的socket实现文件传输 基于TCP的socket结合java的io流 实现客户端与服务器之间的文件传输 Socket 套接字 xff08 socket xff09 是一个抽象层 xff0c 应用程序可以通过它发送或接
  • MySQL索引的创建与使用

    索引的分类 在学习如何创建索引之前 xff0c 先了解一下索引的分类 MySQL中分为 xff1a 普通索引 xff0c 唯一索引 xff0c 主键索引 xff0c 组合索引 xff0c 和全文索引 index name xff1a 索引名
  • ThreadLocal类

    ThreadLocal类 什么是ThreadLocal为什么ThreadLocal是线程安全的呢 什么是ThreadLocal ThreadLocal可以简单的理解为他其实就是一个工具类 xff0c 用来存储线程局部变量的一个工具类 xff
  • spring boot 访问HTML

    HTML整合spring boot 简介默认文件路径访问自定义文件路径访问 或通过Controller控制器层跳转访问 简介 SpringBoot默认的页面映射路径 xff08 即模板文件存放的位置 xff09 为 classpath te
  • HTML重定向解析ModelMap

    HTML实现重定向解析ModelMap 日常开发中 很多场景需要跳转页面 xff0c 又要携带参数 xff0c 重定向就可以起到很好的作用 业务场景 xff1a 登录成功后展示用户信息 登录页面输入用户名 密码进行登录访问 span cla
  • RHCE-ansible(一)--- 安装ansible、主机清单、sudo提权、特权升级

    目录 一 环境配置 1 配置三个主机 etc hosts 文件 xff0c 实现通过域名访问 2 配置SSH远程免密连接 2 1 在控制主机生成密钥 2 2 发送公钥到受控主机 二 受控主机 xff08 xixi xff09 安装ansib
  • 针对opencv导入Android studio不成功的解决办法?

    一 问题如下 xff1a AS gt File gt New gt Import Module 选择导入 压缩包路径 sdk java文件夹 xff0c 然后发现AS没有下一步 xff1f 二 解决办法 新建一个项目 在新建项目下创建一个包
  • Attempt to invoke virtual method ‘void android.widget.TextView.setText(java.lang.CharSequence)‘ on a

    问题简述 xff1a Attempt to invoke virtual method 39 void android widget TextView setText java lang CharSequence 39 on a null
  • WebRTC使用Linux搭建服务器(二)

    搭建服务器流程 xff1a 注意 xff1a 每个人搭建服务器可能会出现奇奇怪怪的问题 xff0c 照着我的方法可能会出现其他问题 xff0c 不要着急 xff0c 耐心搭建 xff0c 确实比较烦 1 安装JDK apt get upda
  • Java基础——有无参数和有无返回值

    一 有无参数 有参数 xff1a 小括号里面有内容 xff0c 当一个方法需要一些数据条件 xff0c 才能完成任务的时候 xff0c 就是有参数 例如两个数字相加 xff0c 必须知道两个数字各自有多少 xff0c 才能相加 无参数 xf
  • STM32学习:利用寄存器点亮LED

    使用普中PZ6806L开发板 由对应的LED模块的电路可知 xff0c 要想点亮一个LED xff0c 就要将其对应的引脚输出低电平 要使用寄存器 xff0c 首先要对其进行封装 xff0c 具体代码如下 xff1a define PERI
  • java基础——求数组长度、遍历数组、求最值和数组元素反转

    一 求数组长度 获取数组的长度的格式 xff1a 数组名称 length 这将会得到一个int数字 xff0c 代表数组的长度 数组一旦创建 xff0c 程序运行期间 xff0c 长度不可改变 代码如下 xff1a public class
  • java基础—Random

    一 概述 Random 类用来生成随机数字 xff0c xff0c 使用起来也是三个步骤 xff1a 1 导包 2 创建 Random r 61 new Random 小括号留空即可 3 使用 获取一个随机数的int的数字 范围是int所有
  • ffmpeg播放器(二)音频解码与播放

    音频解码和播放的前面准备工作和视频的格式差不多 xff0c 创建两个线程分别解码和播放 xff0c 这里统一只放代码了 void AudioChannel play 设置为播放状态 packets setWork 1 frames setW