上篇文章我们利用FFmpeg+ANativeWindwo实现了视频的解码和渲染,已经完成视频画面在SurfaceView上显示。还没阅读上一篇文章的同学建议先阅读:Android FFmpeg开发(二),实现视频解码和渲染

本文我们将对音频流进行解码和渲染,这样就能实现一个较完整的视频播放器的效果。具体技术选型如下:

  • 使用FFmpeg解码音频流
  • 使用OpenSL ES播放音频PCM数据

一、FFmpeg解码音频流

音频流程和视频解码流程整体类似,具体流程大家可以看上一篇文章。本文说一下音频解码相关的特有细节。

首先,是格式转换。上一篇我们也对视频解码做了格式转换(YUV->RGBA)。同样的,音频解码我们也需要按照我们预期的格式进行格式转换,具体如下所示:

AVCodecContext *codecContext = getCodecContext();
mSwrContext = swr_alloc();av_opt_set_int(mSwrContext, "in_channel_layout", codecContext->channel_layout, 0);
av_opt_set_int(mSwrContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);av_opt_set_int(mSwrContext, "in_sample_rate", codecContext->sample_rate, 0);
av_opt_set_int(mSwrContext, "out_sample_rate", 44100, 0);av_opt_set_int(mSwrContext, "in_sample_fmt", codecContext->sample_fmt, 0);
av_opt_set_int(mSwrContext, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);swr_init(mSwrContext);// resample
mNbSample = av_rescale_rnd(ACC_NB_SAMPLES, AUDIO_DST_SAMPLE_RATE,codecContext->sample_rate,AV_ROUND_UP);
mDstFrameDataSize = av_samples_get_buffer_size(NULL, AUDIO_DST_CHANNEL_COUNTS,mNbSample, DST_SAMPLE_FORMAT, 1);
// 分配OpenSL播放音频的帧内存
mAudioOutBuffer = (uint8_t *) malloc(mDstFrameDataSize);

我们需要把原始的音频格式(包括声道、采样率、采样格式)和目标音频的格式(包括声道、采样率、采样格式)传递给av_opt_set_int方法,然后调用swr_init初始化SwrContext上下文出来。最后,调用swr_convert,传入SwrContext即可获得目标格式的音频。如下所示:

void AudioDecoder::onFrameAvailable(AVFrame *frame) {LOGD("AudioDecoder::onFrameAvailable frame=%p, frame->nb_samples=%d\n", frame, frame->nb_samples);if (mAudioRender) {// 将解码出来音频帧进行重采样,采样后数据存入mAudioOutBuffer中int result = swr_convert(mSwrContext, &mAudioOutBuffer,mDstFrameDataSize / 2,(const uint8_t **)frame->data, frame->nb_samples);if (result > 0) {// 利用OpenSL实现音频流渲染mAudioRender->renderAudioFrame(mAudioOutBuffer, mDstFrameDataSize);}}
}

最终,待渲染的音频PCM数据存在mAudioOutBuffer所指定的buffer中。接下来,将这批数据交给OpenSL ES,让OpenSL ES实现音频PCM的播放。

二、OpenSL ES渲染音频

Android的NDK提供了OpenSL ES的C接口,可以提供非常强大的音频处理。Android官方文档:OpenSL ES

在使用OpenSL ES的API之前,需要先引入OpenSL ES的头文件,代码如下:

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

然后编写CMakeLists.txt,在链接阶段链接上OpenSL ES对应的so库:

target_link_libraries( # Specifies the target library.hello-ffmpeg# Links the target library to the log library# included in the NDK.${log-lib}androidOpenSLESffmpeg)

到此,我们就可以使用OpenSL ES的API了。具体使用步骤如下:

1)创建引擎对象接口

int OpenSLRender::createEngine() {SLresult result = SL_RESULT_SUCCESS;do {result = slCreateEngine(&mEngineObj, 0, nullptr,0, nullptr, nullptr);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createEngine slCreateEngine fail. result=%d\n", result);break;}result = (*mEngineObj)->Realize(mEngineObj, SL_BOOLEAN_FALSE);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createEngine Realize fail. result=%d\n", result);break;}result = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, &mEngineEngine);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createEngine GetInterface fail. result=%d\n", result);break;}} while (false);return result;
}

2)接下来创建混音器对象

int OpenSLRender::createOutputMixer() {SLresult result = SL_RESULT_SUCCESS;do {const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};const SLboolean mreg[1] = {SL_BOOLEAN_FALSE};result = (*mEngineEngine)->CreateOutputMix(mEngineEngine, &mOutputMixObj, 1, mids, mreg);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createOutputMixer CreateOutputMix fail. result=%d\n", result);break;}result = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createOutputMixer Realize fail. result=%d\n", result);break;}} while (false);return result;
}

3)创建音频播放对象,为音频播放bufferQueue设置回调

int OpenSLRender::createAudioPlayer() {SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, // format type(SLuint32)2, // channel countSL_SAMPLINGRATE_44_1, // 44100HZSL_PCMSAMPLEFORMAT_FIXED_16, // bits per sampleSL_PCMSAMPLEFORMAT_FIXED_16, // container sizeSL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // channel maskSL_BYTEORDER_LITTLEENDIAN // 小端字节};SLDataSource slDataSource = {&android_queue, &pcm};SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, mOutputMixObj};SLDataSink slDataSink = {&outputMix, nullptr};const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};SLresult result;do {result = (*mEngineEngine)->CreateAudioPlayer(mEngineEngine, &mAudioPlayerObj, &slDataSource,&slDataSink, 3, ids, req);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createAudioPlayer CreateAudioPlayer fail. result=%d\n", result);break;}result = (*mAudioPlayerObj)->Realize(mAudioPlayerObj, SL_BOOLEAN_FALSE);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createAudioPlayer Realize mAudioPlayerObj fail. result=%d\n",result);break;}result = (*mAudioPlayerObj)->GetInterface(mAudioPlayerObj, SL_IID_PLAY, &mAudioPlayerPlay);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createAudioPlayer GetInterface SL_IID_PLAY fail. result=%d\n",result);break;}result = (*mAudioPlayerObj)->GetInterface(mAudioPlayerObj, SL_IID_BUFFERQUEUE,&mBufferQueue);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createAudioPlayer GetInterface SL_IID_BUFFERQUEUE fail. result=%d\n",result);break;}result = (*mBufferQueue)->RegisterCallback(mBufferQueue, audioPlayerCallback, this);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createAudioPlayer RegisterCallback fail. result=%d\n", result);break;}result = (*mAudioPlayerObj)->GetInterface(mAudioPlayerObj, SL_IID_VOLUME,&mAudioPlayerVolume);if (result != SL_RESULT_SUCCESS) {LOGD("OpenSLRender::createAudioPlayer GetInterface SL_IID_VOLUME fail. result=%d\n",result);break;}} while (false);return result;
}

上面核心流程有一步是为SLAndroidSimpleBufferQueueItf注册回调函数,这个回调函数当OpenSL ES需要数据进行播放时会调用,我们需要在回调函数中填充PCM数据。

4)填充PCM数据,通过SLAndroidSimpleBufferQueueItf接口的Enqueue方法填充音频PCM数据,如下:

// SLAndroidSimpleBufferQueueItf
(*mBufferQueue)->Enqueue(mBufferQueue, audioFrame->data, (SLuint32)audioFrame->dataSize);

5)播放

// SLPlayItf
(*mAudioPlayerPlay)->SetPlayState(mAudioPlayerPlay, SL_PLAYSTATE_PLAYING);

6)最后,资源释放

void OpenSLRender::unInit() {LOGD("OpenSLRender::unInit");if (mAudioPlayerPlay) {(*mAudioPlayerPlay)->SetPlayState(mAudioPlayerPlay, SL_PLAYSTATE_STOPPED);mAudioPlayerPlay = nullptr;}if (mAudioPlayerObj) {(*mAudioPlayerObj)->Destroy(mAudioPlayerObj);mAudioPlayerObj = nullptr;mBufferQueue = nullptr;}if (mOutputMixObj) {(*mOutputMixObj)->Destroy(mOutputMixObj);mOutputMixObj = nullptr;}if (mEngineObj) {(*mEngineObj)->Destroy(mEngineObj);mEngineObj = nullptr;mEngineEngine = nullptr;}
}

三、总结

本文利用FFmepg+OpenSL ES实现音频流解码和播放,至此一款简单的视频播放器就已经做出来了。

源码链接

git clone git@github.com:lorienzhang/HelloFFmpeg.git
# 检出 v3 tag 进行查看
git checkout v3

Android FFmpeg开发(三),利用OpenSL ES实现音频渲染相关推荐

  1. 音视频开发之旅(36) -FFmpeg +OpenSL ES实现音频解码和播放

    目录 OpenSL ES基本介绍 OpenSL ES播放音频流程 代码实现 遇到的问题 资料 收获 上一篇我们通过AudioTrack实现了FFmpeg解码后的PCM音频数据的播放,在Android上 ...

  2. Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  3. Android通过OpenSL ES播放音频套路详解

    我的视频课程(基础):<(NDK)FFmpeg打造Android万能音频播放器> 我的视频课程(进阶):<(NDK)FFmpeg打造Android视频播放器> 我的视频课程(编 ...

  4. Android音视频【十三】OpenSL ES介绍基于OpenSL ES实现音频采集

    人间观察 勿再别人的心中修行自己, 勿再自己的心中强求别人. 前言 最近写文章有点偷懒了,离上次写文章大概一个月了. 一般Android音频的采集在java层使用AudioRecord类进行采集. 但 ...

  5. 【Android FFMPEG 开发】OpenSLES 播放音频 ( 创建引擎 | 输出混音设置 | 配置输入输出 | 创建播放器 | 获取播放/队列接口 | 回调函数 | 开始播放 | 激活回调 )

    文章目录 I . FFMPEG 播放视频流程 II . OpenSLES 播放音频流程 III . OpenSLES 播放参考 Google 官方示例 IV . OpenSL ES 播放代码 ( 详细 ...

  6. 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

    文章目录 I . FFMPEG 播放视频流程 II . FFMPEG 音频重采样流程 III . FFMPEG 音频重采样 IV . FFMPEG 初始化音频重采样上下文 SwrContext V . ...

  7. 【Android FFMPEG 开发】Android 中使用 FFMPEG 对 MP3 文件进行混音操作

    文章目录 一.前置操作 ( 移植 FFMPEG ) 二.FFMPEG 混音命令 三.Android FFMPEG 混音源代码完整示例 四.博客源码 一.前置操作 ( 移植 FFMPEG ) 参考 [A ...

  8. 【Android FFMPEG 开发】Android 中使用 FFMPEG 将 PCM 音频采样转为 MP3 格式

    文章目录 一.前置操作 ( 移植 FFMPEG ) 二.FFMPEG 将 PCM 采样转为 MP3 格式的命令 三.Android FFMPEG 混音源代码完整示例 四.博客源码 一.前置操作 ( 移 ...

  9. 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( 设置 ANativeWindow 缓冲区属性 | 获取绘制缓冲区 | 填充数据到缓冲区 | 启动绘制 )

    文章目录 I . FFMPEG ANativeWindow 原生绘制 前置操作 II . FFMPEG 原生绘制流程 III . 设置 ANativeWindow 绘制窗口属性 ANativeWind ...

最新文章

  1. 滑动平均滤波_11种滤波算法程序大全(含源代码分享)
  2. 互联网公司IT系统架构进化之路
  3. Java程序员从笨鸟到菜鸟之(七十五)细谈struts2(十四)struts2+ajax实现异步验证...
  4. 第五章 常用Lua开发库2-JSON库、编码转换、字符串处理
  5. 【数据库原理及应用】经典题库附答案(14章全)——第八章:数据库并发控制
  6. java代码快速_java代码编写快捷途经
  7. POJ-1861-Network 解题报告
  8. 爆料者称苹果仍在继续研发iPhone屏下Touch ID
  9. 网站未备案不能访问,怎么用ip加端口的方式建站?
  10. Linux下通过HostName访问主机以及修改HostName方法
  11. Java使用apache commons连接ftp修改ftp文件名失败原因
  12. 利用Hownet进行语义相似度计算的类(
  13. 栈:后进先出的线性表
  14. 物联网操作系统Zephyr(入门篇)之1.0 Zephyr简介
  15. AID Learning设置aidcode的启动页面
  16. 面试题:fail-safe 机制与 fail-fast 机制分别有什 么作用
  17. 常见的数据可视化方式
  18. 独角推荐,只需一个邮箱号就可以注册购买阿里云国际版
  19. 基于Spring boot的图书馆图书借阅管理系统的设计与实现
  20. cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

热门文章

  1. web开发——前端基础(1)—— 第一个网页的展示
  2. blazeds TypeError: Error #1034: 强制转换类型失败
  3. uni.app小程序的ajax封装详细讲解
  4. LyricEase 永久停服!可惜了!
  5. 我的创作两周年纪念日
  6. c语言结构体指针使用方法,C语言结构体指针的使用方法
  7. centos下安装mysql选什么版本_CentOS 7 安装MySQL 5.7 或安装指定版本MySQL-Go语言中文社区...
  8. 数据分析-深度学习Pytorch Day2
  9. The dimen in values has no declaration in the base values folder; this can lead to crashes when the
  10. 图片识别文字怎么做?这几种方法轻松解决