关于
Android使用FFmpeg(一)--编译ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(四)--ffmpeg实现音频播放(使用AudioTrack进行播放)
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
Android使用FFmpeg(七)--ffmpeg实现暂停、快退快进播放
准备工作
正文
实现整体思路:使用ffmpeg解封装、解码视频得到pcm数据-->数据添加到opensl es缓冲区中-->播放
实现具体思路:因为在上篇中详细讲解了将视频通过ffmpeg解码成pcm,所以在这篇中将详细讲解如何通过opensl es播放。
我们还是根据流程图来进行代码的编写,注释已经写好,代码不懂之处可以看注释:
1.创建引擎:
//创建引擎
void createEngine(){
slCreateEngine(&engineObject,0,NULL,0,NULL,NULL);//创建引擎
(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);//实现engineObject接口对象
(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineEngine);//通过引擎调用接口初始化SLEngineItf
}
2.创建混音器:
//创建混音器
void createMixVolume(){
(*engineEngine)->CreateOutputMix(engineEngine,&outputMixObject,0,0,0);//用引擎对象创建混音器接口对象
(*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE);//实现混音器接口对象
SLresult sLresult = (*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);//利用混音器实例对象接口初始化具体的混音器对象
//设置
if (SL_RESULT_SUCCESS == sLresult) {
(*outputMixEnvironmentalReverb)->
SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &settings);
}
}
3.创建播放器并播放:
void createPlayer(){
//初始化ffmpeg
int rate;
int channels;
createFFmpeg(&rate,&channels);
LOGE("RATE %d",rate);
LOGE("channels %d",channels);
/*
* typedef struct SLDataLocator_AndroidBufferQueue_ {
SLuint32 locatorType;//缓冲区队列类型
SLuint32 numBuffers;//buffer位数
} */
SLDataLocator_AndroidBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
/**
typedef struct SLDataFormat_PCM_ {
SLuint32 formatType; pcm
SLuint32 numChannels; 通道数
SLuint32 samplesPerSec; 采样率
SLuint32 bitsPerSample; 采样位数
SLuint32 containerSize; 包含位数
SLuint32 channelMask; 立体声
SLuint32 endianness; end标志位
} SLDataFormat_PCM;
*/
SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM,channels,rate*1000
,SL_PCMSAMPLEFORMAT_FIXED_16
,SL_PCMSAMPLEFORMAT_FIXED_16
,SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,SL_BYTEORDER_LITTLEENDIAN};
/*
* typedef struct SLDataSource_ {
void *pLocator;//缓冲区队列
void *pFormat;//数据样式,配置信息
} SLDataSource;
* */
SLDataSource dataSource = {&android_queue,&pcm};
SLDataLocator_OutputMix slDataLocator_outputMix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject};
SLDataSink slDataSink = {&slDataLocator_outputMix,NULL};
const SLInterfaceID ids[3]={SL_IID_BUFFERQUEUE,SL_IID_EFFECTSEND,SL_IID_VOLUME};
const SLboolean req[3]={SL_BOOLEAN_FALSE,SL_BOOLEAN_FALSE,SL_BOOLEAN_FALSE};
/*
* SLresult (*CreateAudioPlayer) (
SLEngineItf self,
SLObjectItf * pPlayer,
SLDataSource *pAudioSrc,//数据设置
SLDataSink *pAudioSnk,//关联混音器
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
* */
LOGE("执行到此处")
(*engineEngine)->CreateAudioPlayer(engineEngine,&audioplayer,&dataSource,&slDataSink,3,ids,req);
(*audioplayer)->Realize(audioplayer,SL_BOOLEAN_FALSE);
LOGE("执行到此处2")
(*audioplayer)->GetInterface(audioplayer,SL_IID_PLAY,&slPlayItf);//初始化播放器
//注册缓冲区,通过缓冲区里面 的数据进行播放
(*audioplayer)->GetInterface(audioplayer,SL_IID_BUFFERQUEUE,&slBufferQueueItf);
//设置回调接口
(*slBufferQueueItf)->RegisterCallback(slBufferQueueItf,getQueueCallBack,NULL);
//播放
(*slPlayItf)->SetPlayState(slPlayItf,SL_PLAYSTATE_PLAYING);
//开始播放
getQueueCallBack(slBufferQueueItf,NULL);
}
//回调的函数
void getQueueCallBack(SLAndroidSimpleBufferQueueItf slBufferQueueItf, void* context){
buffersize=0;
getPcm(&buffer,&buffersize);
if(buffer!=NULL&&buffersize!=0){
//将得到的数据加入到队列中
(*slBufferQueueItf)->Enqueue(slBufferQueueItf,buffer,buffersize);
}
}
4.释放资源,从下往上依次释放:
void realseResource(){
if(audioplayer!=NULL){
(*audioplayer)->Destroy(audioplayer);
audioplayer=NULL;
slBufferQueueItf=NULL;
slPlayItf=NULL;
}
if(outputMixObject!=NULL){
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject=NULL;
outputMixEnvironmentalReverb=NULL;
}
if(engineObject!=NULL){
(*engineObject)->Destroy(engineObject);
engineObject=NULL;
engineEngine=NULL;
}
realseFFmpeg();
}
5.关于使用ffmpeg得到pcm,我们将其单独写入一个cpp文件中,分为三部分:创建,得到pcm,释放资源。方便在openSL ES这边进行一个调用。具体代码的注释和思路在上篇中已经讲解,不懂的请看上篇的内容。这里的代码只是将整体的代码进行了分类和模块化。
//
// Created by david on 2017/9/25.
//
#include "FFmpegMusic.h"
AVFormatContext *pFormatCtx;
AVCodecContext *pCodecCtx;
AVCodec *pCodex;
AVPacket *packet;
AVFrame *frame;
SwrContext *swrContext;
uint8_t *out_buffer;
int out_channer_nb;
int audio_stream_idx=-1;
//opensl es调用 int * rate,int *channel
int createFFmpeg(int *rate,int *channel){
av_register_all();
char *input = "/sdcard/input.mp3";
pFormatCtx = avformat_alloc_context();
LOGE("Lujng %s",input);
LOGE("xxx %p",pFormatCtx);
int error;
char buf[] = "";
//打开视频地址并获取里面的内容(解封装)
if (error = avformat_open_input(&pFormatCtx, input, NULL, NULL) < 0) {
av_strerror(error, buf, 1024);
// LOGE("%s" ,inputPath)
LOGE("Couldn't open file %s: %d(%s)", input, error, buf);
// LOGE("%d",error)
LOGE("打开视频失败")
}
//3.获取视频信息
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGE("%s","获取视频信息失败");
return -1;
}
int i=0;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
LOGE(" 找到音频id %d", pFormatCtx->streams[i]->codec->codec_type);
audio_stream_idx=i;
break;
}
}
// mp3的解码器
// 获取音频编解码器
pCodecCtx=pFormatCtx->streams[audio_stream_idx]->codec;
LOGE("获取视频编码器上下文 %p ",pCodecCtx);
pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
LOGE("获取视频编码 %p",pCodex);
if (avcodec_open2(pCodecCtx, pCodex, NULL)<0) {
}
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
// av_init_packet(packet);
// 音频数据
frame = av_frame_alloc();
// mp3 里面所包含的编码格式 转换成 pcm SwcContext
swrContext = swr_alloc();
int length=0;
int got_frame;
// 44100*2
out_buffer = (uint8_t *) av_malloc(44100 * 2);
uint64_t out_ch_layout=AV_CH_LAYOUT_STEREO;
// 输出采样位数 16位
enum AVSampleFormat out_formart=AV_SAMPLE_FMT_S16;
//输出的采样率必须与输入相同
int out_sample_rate = pCodecCtx->sample_rate;
swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0,
NULL);
swr_init(swrContext);
// 获取通道数 2
out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
*rate = pCodecCtx->sample_rate;
*channel = pCodecCtx->channels;
return 0;
}
//
int getPcm(void **pcm,size_t *pcm_size){
int frameCount=0;
int got_frame;
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == audio_stream_idx) {
// 解码 mp3 编码格式frame----pcm frame
avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
if (got_frame) {
LOGE("解码");
/**
* int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
const uint8_t **in , int in_count);
*/
swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
// 缓冲区的大小
int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
*pcm = out_buffer;
*pcm_size = size;
break;
}
}
}
return 0;
}
void realseFFmpeg(){
av_free_packet(packet);
av_free(out_buffer);
av_frame_free(&frame);
swr_free(&swrContext);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}
小结
按照流程图来编写代码清晰明朗,只是一些细节的地方注意就行。另外注意以下几点:
1.测试时请使用真机测试,因为没有使用x86的cpu的.so;
2.注意添加权限;
3.注意将music.cpp添加到CMakeLists.txt中;
4.测试时请在手机中存入input.xxx的音频文件。
源码地址