Android音视频学习第7章:使用OpenSL ES进行音频解码

/*

*

*这里使用了transcode-1.1.7对wav文件进行解码,然后使用opensl es进行播放

*

*/



//用到的变量和结构体

WAV wav; //wav文件指针
SLObjectItf engineObject; //引擎对象
SLEngineItf engineInterface; //引擎接口
SLObjectItf outputMixObject; //混音器
SLObjectItf audioPlayerObject; //播放器对象
SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf;    //缓冲器队列接口
SLPlayItf audioPlayInterface;   //播放接口

unsigned char *buffer; //缓冲区
size_t bufferSize;     //缓冲区大小

//上下文
struct PlayerContext{
    WAV wav;
    unsigned char *buffer;
    size_t bufferSize;

    PlayerContext(WAV wav,
            unsigned char *buffer,
            size_t bufferSize){
        this->wav = wav;
        this->buffer = buffer;
        this->bufferSize = bufferSize;
    }
};
//实现对象
void RealizeObject(SLObjectItf object){
    //非异步(阻塞)
    (*object)->Realize(object,SL_BOOLEAN_FALSE);
}

具体实现流程:

1.打开文件

WAV wav = OpenWaveFile(env,jFileName);
//打开文件
WAV OpenWaveFile(JNIEnv *env,jstring jFileName){
    const char *cFileName = env->GetStringUTFChars(jFileName,JNI_FALSE);
    WAVError err;
    WAV wav = wav_open(cFileName,WAV_READ,&err);

    LOGI("%d",wav_get_bitrate(wav));
    env->ReleaseStringUTFChars(jFileName,cFileName);
    if(wav == 0){
        LOGE("%s",wav_strerror(err));
    }
    return wav;
}

2.创建OpenSL ES引擎

//OpenSL ES在Android平台下默认是线程安全的,这样设置是为了为了兼容其他平台
    SLEngineOption options[] = {
        {(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
    };
    slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0); //没有接口

//实例化对象
    //对象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
    RealizeObject(engineObject);

3.获取引擎接口

(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);

4.创建输出混音器

(*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口
    //实例化混音器
    RealizeObject(outputMixObject);

5.创建缓冲区保存读取到的音频数据库

//缓冲区的大小
    bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
    buffer = new unsigned char[bufferSize];

6.创建带有缓冲区队列的音频播放器

CreateBufferQueueAudioPlayer(wav,engineInterface,outputMixObject,audioPlayerObject);
    //实例化音频播放器
    RealizeObject(audioPlayerObject);

CreateBufferQueueAudioPlayer.cpp

extern "C" {
#include "wavlib.h"
}
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>

#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
//创建音频播放对象
void CreateBufferQueueAudioPlayer(
        WAV wav,
        SLEngineItf engineEngine,
        SLObjectItf outputMixObject,
        SLObjectItf &audioPlayerObject){

    // Android针对数据源的简单缓冲区队列定位器
    SLDataLocator_AndroidSimpleBufferQueue dataSourceLocator = {
        SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // 定位器类型
        1                                        // 缓冲区数
    };

    // PCM数据源格式
    SLDataFormat_PCM dataSourceFormat = {
        SL_DATAFORMAT_PCM,        // 格式类型
        wav_get_channels(wav),    // 通道数
        wav_get_rate(wav) * 1000, // 毫赫兹/秒的样本数
        wav_get_bits(wav),        // 每个样本的位数
        wav_get_bits(wav),        // 容器大小
        SL_SPEAKER_FRONT_CENTER,  // 通道屏蔽
        SL_BYTEORDER_LITTLEENDIAN // 字节顺序
    };

    // 数据源是含有PCM格式的简单缓冲区队列
    SLDataSource dataSource = {
        &dataSourceLocator, // 数据定位器
        &dataSourceFormat   // 数据格式
    };

    // 针对数据接收器的输出混合定位器
    SLDataLocator_OutputMix dataSinkLocator = {
        SL_DATALOCATOR_OUTPUTMIX, // 定位器类型
        outputMixObject           // 输出混合
    };

    // 数据定位器是一个输出混合
    SLDataSink dataSink = {
        &dataSinkLocator, // 定位器
        0                 // 格式
    };

    // 需要的接口
    SLInterfaceID interfaceIds[] = {
        SL_IID_BUFFERQUEUE
    };

    // 需要的接口,如果所需要的接口不要用,请求将失败
    SLboolean requiredInterfaces[] = {
        SL_BOOLEAN_TRUE // for SL_IID_BUFFERQUEUE
    };

    // 创建音频播放器对象
    SLresult result = (*engineEngine)->CreateAudioPlayer(
            engineEngine,
            &audioPlayerObject,
            &dataSource,
            &dataSink,
            ARRAY_LEN(interfaceIds),
            interfaceIds,
            requiredInterfaces);
}

7.获得缓冲区队列接口Buffer Queue Interface

//通过缓冲区队列接口对缓冲区进行排序播放
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);

8.注册音频播放器回调函数

//当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束
    //上下文,包裹参数方便再回调函数中使用
    PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
    (*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);
//回调函数
void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
    PlayerContext* ctx = (PlayerContext*)context;
    //读取数据
    ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
    if(0 < readSize){
        (*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
    }else{
        //destroy context
        CloseWaveFile(ctx->wav); //关闭文件
        delete ctx->buffer; //释放缓存
    }
}

9.获取Play Interface通过对SetPlayState函数来启动播放音乐

//一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
    //设置播放状态
    (*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);

10.开始,让第一个缓冲区入队

PlayerCallBack(andioPlayerBufferQueueItf,ctx);

完整代码

#include "com_dongnaoedu_jasonaudioplayer_AudioPlayer.h"

extern "C" {
#include "wavlib.h"
}
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#include "CreateBufferQueueAudioPlayer.cpp"

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"jason",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"jason",FORMAT,##__VA_ARGS__);

#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))

WAV wav; //wav文件指针
SLObjectItf engineObject; //引擎对象
SLEngineItf engineInterface; //引擎接口
SLObjectItf outputMixObject; //混音器
SLObjectItf audioPlayerObject; //播放器对象
SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf;    //缓冲器队列接口
SLPlayItf audioPlayInterface;   //播放接口

unsigned char *buffer; //缓冲区
size_t bufferSize;     //缓冲区大小

//上下文
struct PlayerContext{
    WAV wav;
    unsigned char *buffer;
    size_t bufferSize;

    PlayerContext(WAV wav,
            unsigned char *buffer,
            size_t bufferSize){
        this->wav = wav;
        this->buffer = buffer;
        this->bufferSize = bufferSize;
    }
};

//打开文件
WAV OpenWaveFile(JNIEnv *env,jstring jFileName){
    const char *cFileName = env->GetStringUTFChars(jFileName,JNI_FALSE);
    WAVError err;
    WAV wav = wav_open(cFileName,WAV_READ,&err);

    LOGI("%d",wav_get_bitrate(wav));
    env->ReleaseStringUTFChars(jFileName,cFileName);
    if(wav == 0){
        LOGE("%s",wav_strerror(err));
    }
    return wav;
}

//关闭文件
void CloseWaveFile(WAV wav){
    wav_close(wav);
}

//实现对象
void RealizeObject(SLObjectItf object){
    //非异步(阻塞)
    (*object)->Realize(object,SL_BOOLEAN_FALSE);
}

//回调函数
void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
    PlayerContext* ctx = (PlayerContext*)context;
    //读取数据
    ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
    if(0 < readSize){
        (*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
    }else{
        //destroy context
        CloseWaveFile(ctx->wav); //关闭文件
        delete ctx->buffer; //释放缓存
    }
}

JNIEXPORT void JNICALL Java_com_dongnaoedu_jasonaudioplayer_AudioPlayer_play
  (JNIEnv *env, jclass jthiz, jstring jFileName){
    //1.打开文件
    WAV wav = OpenWaveFile(env,jFileName);

    //2.创建OpenSL ES引擎
    //OpenSL ES在Android平台下默认是线程安全的,这样设置是为了为了兼容其他平台
    SLEngineOption options[] = {
        {(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
    };
    slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0); //没有接口

    //实例化对象
    //对象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
    RealizeObject(engineObject);

    //3.获取引擎接口
    (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);

    //4.创建输出混音器
    (*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口

    //实例化混音器
    RealizeObject(outputMixObject);

    //5.创建缓冲区保存读取到的音频数据库
    //缓冲区的大小
    bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
    buffer = new unsigned char[bufferSize];

    //6.创建带有缓冲区队列的音频播放器
    CreateBufferQueueAudioPlayer(wav,engineInterface,outputMixObject,audioPlayerObject);

    //实例化音频播放器
    RealizeObject(audioPlayerObject);

    //7.获得缓冲区队列接口Buffer Queue Interface
    //通过缓冲区队列接口对缓冲区进行排序播放
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);

    //8.注册音频播放器回调函数
    //当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束
    //上下文,包裹参数方便再回调函数中使用
    PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
    (*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);

    //9.获取Play Interface通过对SetPlayState函数来启动播放音乐
    //一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
    //设置播放状态
    (*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);

    //10.开始,让第一个缓冲区入队
    PlayerCallBack(andioPlayerBufferQueueItf,ctx);

    //关闭文件
    //CloseWaveFile(wav);
}
时间: 2024-09-26 20:18:51

Android音视频学习第7章:使用OpenSL ES进行音频解码的相关文章

Android 音视频深入 十四 FFmpeg与OpenSL ES 播放mp3音乐,能暂停(附源码

项目地址https://github.com/979451341/FFmpegOpenslES 这次说的是FFmpeg解码mp3,数据给OpenSL ES播放,并且能够暂停.1.创建引擎 slCreateEngine(&engineObject,0,NULL,0,NULL,NULL);//创建引擎 (*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);//实现engineObject接口对象 (*engineObject)->G

Android 音视频开发学习思路

Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的.只能通过一点点的学习和积累把这块的知识串联积累起来.在学习的过程中,遇到不懂的地方,要及时的去google或者请教大神,不要半知半解的过去. 初级入门篇: 在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView,SurfaceView,自定义 View 在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采

Android 音视频深入 七 学习之路的总结和资料分享

说个实话一开始我对基于Android如何开发音视频很迷茫,甚至对音视频开发都不是很明白,我看了Android 音视频开发入门指南 http://blog.51cto.com/ticktick/1956269这篇博客,我觉得我也许不太懂音视频能干啥,但是我找到了学习音视频的门槛,对了写这个博客的人,他的博客很多都是关于音视频的很有借鉴 开始把Android原生音频API AudioFormat.AudioRecord.AudioTrack底层三剑客搞一下,不说代码非常熟,至少整个作用和相关的参数和

android音视频点/直播模块开发

前言 随着音视频领域的火热,在很多领域(教育,游戏,娱乐,体育,跑步,餐饮,音乐等)尝试做音视频直播/点播功能,那么作为开发一个小白,如何快速学习音视频基础知识,了解音视频编解码的传输协议,编解码方式,以及如何技术选型,如何解决遇到的坑,本文抛砖引玉,欢迎大咖交流. 一. 音视频的基础知识 1.1 基本概念 视频是什么 静止的画面叫图像(picture).连续的图像变化每秒超过24帧(frame)画面以上时,根椐视觉暂留原理, 人眼无法辨别每付单独的静态画面,看上去是平滑连续的视觉效果.这样的连

Android 音视频开发入门指南

最近收到很多网友通过邮件或者留言说想学习音视频开发,该如何入门,我今天专门写篇文章统一回复下吧. 音视频这块,目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的,希望我后面能挤出时间整一个专题详细讲一讲--目前的话,我先给出一个大的方向性的学习指南,希望对初学者有所帮助. 我一直相信带着 "任务" 去学习和实践,效率会高很多,因此我列出了一系列音视频相关的 "开发任务",从简单到困难(当然,不一定非常严格和完美,部分任务先后可调整),大家在完成任务的

Android音视频点/直播模块开发实践总结-zz

随着音视频领域的火热,在很多领域(教育,游戏,娱乐,体育,跑步,餐饮,音乐等)尝试做音视频直播/点播功能.那么作为开发一个小白,如何快速学习音视频基础知识,了解音视频编解码的传输协议,编解码方式,以及如何技术选型,如何解决遇到的坑. 一. 音视频的基础知识 1.1 基本概念 视频是什么 静止的画面叫图像(picture).连续的图像变化每秒超过24帧(frame)画面以上时,根椐视觉暂留原理,人眼无法辨别每付单独的静态画面,看上去是平滑连续的视觉效果.这样的连续画面叫视频.当连续图像变化每秒低于

手机Android音视频采集与直播推送,实现单兵、移动监控类应用

恰逢2014 Google I/O大会,不难看出安卓在Google的推进以及本身的开放性作用下,已经快延生到生活的各个方面了,从安卓智能手机.平板,到可穿戴的Android Ware.眼镜.手表.再到Android汽车.智能家居.电视,甚至最近看新闻,日本出的几款机器人都是Android系统的,再把目光放回监控行业,传统监控中的移动终端设备,例如:单兵设备.手持设备.车载终端设备,包括家庭监控中用到的智能设备,都可以用Android系统替代了,不仅开发容易,而且易扩展,设备也更加智能了. 图 -

Android音视频即时通讯软件怎样通过JNI快速实现

Android音视频即时通讯软件怎样通过JNI快速实现 音视频通信 作为独立开发者或想缩短音视频开发周期的公司来说,想要在Android平台下实现音视频通信,最快捷的方法是寻找开源项目或调用其他公司API.之所以这么说是因为音视频通信技术涉及到底层音视频采集.解码. FFmpeg(音视频处理解决方案).媒体流传输协议等太多太多相关技术知识点.试了几个开源项目,视频差强人意,语音与视频不同步等不稳定因素.因此我把目光放到其他公司的API上(点击下载demo程序).demo程序API提供了一系列纯J

Android 音视频技术之录音获取实时音量

一.实时音量相关基础知识 说到获取音量,大家首先想到的应该就是分贝(dB),分贝是一个相对单位(是一个比值,是一个数值,是一个纯计数方法). 在音频领域dB度量的是声音的强度,其计算的公式如下: 在上面的公式中,分子是测量值的声压,分母是参考值的声压(20微帕,人类所能听到的最小声压). 在Android设备传感器中,我们能获取到的物理值是振幅值,一般使用下面的公式来计算分贝值: 我们从Android SDK中读取了某段音频数据的振幅后,取最大振幅或平均振幅(可以用平方和平均,或绝对值的和平均)