Android- 音视频学习系列 -(十) 基于 -FFmpeg-+-OpenSLES- 实现音频万能播放器
//对应的关闭流信息,释放所有内容资源 void avformat_close_input(AVFormatContext **s);
//3. 读取媒体文件的数据包以获取流信息。返回 >=0 则成功 int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);//3.1 拿到当前流的数量信息,一般会有音频,视频,或者弹幕 int number = (*AVFormatContext)->nb_streams//3.2 遍历拿到对应的 stream//视频流 if ((*pFormatCtx)->streams && (*pFormatCtx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)//语音流 if ((*pFormatCtx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)//其它流类型 enum AVMediaType {AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATAAVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO,AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuousAVMEDIA_TYPE_SUBTITLE,AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparseAVMEDIA_TYPE_NB};
//4. 根据 AVCodecID 拿到已经注册的解码器 AVCodec *avcodec_find_decoder(enum AVCodecID id);
//5. 分配一个 AVCodecContextAVCodecContext *avcodec_alloc_context3(const AVCodec *codec);//对应的释放 void avcodec_free_context(AVCodecContext **avctx);
//6. 给解码器设置参数 int avcodec_parameters_to_context(AVCodecContext *codec,const AVCodecParameters *par);
//7. 打开解码器 is 0 successint avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
以上 7 步如果没有问题证明编解码器打开成功,可以进行下一步操作。
FFmpeg 读取音频帧
这里还是介绍 API 使用:
//1. 分配一个 AVPacketAVPacket *av_packet_alloc(void);//结果必须释放 void av_packet_free(AVPacket **pkt);
//2. 读取待解码数据包 int av_read_frame(AVFormatContext *s, AVPacket *pkt);
对,就是这么简单,就调用 3 个 API 然后循环读取,送入待解码队列中。
FFmpeg 解码音频为 PCM
这里相当于是读取待解码队列中的数据,进行解码为 PCM 数据
//1. 将待解码数据 AVPacket 送入解码器 0 is okint avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
//2. 分配一个 AVFrame 用于接收解码之后的数据 AVFrame *av_frame_alloc(void);//对应的释放 APIvoid av_frame_free(AVFrame **frame);
//3. 接收解码之后的数据 0 is okint avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
//对解码之后的 PCM 进行统一重采样。规定一些格式,避免不统一而渲染异常//4. 根据传入的参数来分配一个 SwrContextstruct SwrContext *swr_alloc_set_opts(struct SwrContext *s,int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate,int log_offset, void *log_ctx);
//4.1 对 SwrContext 进行初始化 int swr_init(struct SwrContext *s);//4.2 开始重采样 int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,const uint8_t **in , int in_count);
OpenSLES 渲染 PCM
这里还是以流程的形式介绍 API 含义
//1. 创建播放引擎 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
//2. 创建混音器 const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);if (SL_RESULT_SUCCESS == result) {result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &reverbSettings);(void) result;}SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};SLDataSink audioSnk = {&outputMix, 0};
//3. 配置 PCM 格式信息 SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM,//播放 pcm 格式的数据 2,//2 个声道(立体声)static_cast<SLuint32>(getCurSampleRate(sample_rate)),//44100hz 的频率 SL_PCMSAMPLEFORMAT_FIXED_16,//位数 16 位 SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一致就行 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立体声(前左前右)SL_BYTEORDER_LITTLEENDIAN//结束标志};
SLDataSource slDataSource = {&android_queue, &pcm};
const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_MUTESOLO};const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slDataSource, &audioSnk,sizeof(ids) / sizeof(ids[0]), ids, req);
//4. 初始化播放器 result = (*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);result = (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);
//5. 注册回调缓冲区 获取缓冲队列接口(*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue);(*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcmBufferCallBack, this);
//6. 设置播放状态(*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);
//7. 手动激活回调接口 pcmBufferCallBack(pcmBufferQueue, this);
初始化就是这 7 大步,那么渲染的话,就是在 pcmBufferCallBack
中进行设置,直接上代码吧:
void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void *pVoid) {auto audioPlayer = static_cast<BaseAudioChannel *>(pVoid);if (!audioPlayer)return;if (audioPlayer->status && audioPlayer->status->exit)LOGE("looper pcmBufferCallBack start");//拿到 PCM 原始数据 int size = audioPlayer->getPCMData();
//对 PCM 做变速变调操作。size = audioPlayer->setSoundTouchData();
...//8. 放入缓存,开始播放声音(*audioPlayer->pcmBufferQueue)->Enqueue(audioPlayer->pcmBufferQueue, audioPlayer->out_pcm_buffer, size);
...}
对,没错。第八步就是真正将 PCM 放入 OpenSL ES 缓冲队列中,这里要注意,一点要等它的上一帧渲染完在放入下一帧 PCM 数据。
功能点实现:
声道选择
声道操作直接操作的是 OpenSLES 接口,具体 API 如下:
//1. 得到音频声道通道接口(pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_MUTESOLO, &pcmChannelModePlay);//2. 设置音频通道/*
设置音频通道
@param channelMode*/void BaseAudioChannel::setChannelMode(int channelMode) {this->mChannelMode = channelMode;if (pcmChannelModePlay != NULL) {if (channelMode == 0)//右声道{(*pcmChannelModePlay)->SetChannelMute(pcmChannelModePlay, 1, false);(*pcmChannelModePlay)->SetChannelMute(pcmChannelModePlay, 0, true);} else if (channelMode == 1)//左声道{(*pcmChannelModePlay)->SetChannelMute(pcmChannelModePlay, 1, true);(*pcmChannelModePlay)->SetChannelMute(pcmChannelModePlay, 0, false);} else if (channelMode == 2)//立体声 通道为 2 也就是我们重采样设置的 AV_CH_LAYOUT_STEREO{(*pcmChannelModePlay)->SetChannelMute(pcmChannelModePlay, 1, false);(*pcmChannelModePlay)->SetChannelMute(pcmChannelModePlay, 0, false);}}}
音量控制
声音音量控制这里还是基于的是 OpenSLES 接口,对应 API 如下:
//1. 拿到音频声音控制接口(pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_VOLUME, &pcmVolumePlay);//2. 设置声音/*
平滑设置当前音量
@param volume*/void BaseAudioChannel::setVolume(int percent) {this->curVolume = percent;if (pcmVolumePlay != NULL) {if (percent > 30) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -20);} else if (percent > 25) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -22);} else if (percent > 20) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -25);} else if (percent > 15) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -28);} else if (percent > 10) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -30);} else if (percent > 5) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -34);} else if (percent > 3) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -37);} else if (percent > 0) {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -40);} else {(*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -100);}}}
语调语速设置
语调语速功能这里用的开源的 SoundTouch ,具体实现如下:
int BaseAudioChannel::setSoundTouchData() {int num = 0;while (status && !status->exit) {if (finished) {finished = false;if (this->mBufSize > 0 && this->out_pcm_buffer) {pthread_mutex_lock(&mutexSpeed);soundTouch->putSamples(reinterpret_cast<const SAMPLETYPE *>(this->out_pcm_buffer), this->oldSize);num = soundTouch->receiveSamples(reinterpret_cast<SAMPLETYPE *>(this->out_pcm_buffer),this->mBufSize / 4);pthread_mutex_unlock(&mutexSpeed);} else {soundTouch->flush();}}if (num == 0) {finished = true;continue;}return num * 2 * 2;}
return 0;}
seek 指定在某个时间段播放
seek 功能直接调取的 FFmpeg API ,操作如下:
void BaseDecodec::seek(int number) {if (duration <= 0) {return;}if (number >= 0 && numbe
r <= number) {int64_t rel = number * AV_TIME_BASE;avcodec_flush_buffers(this->avCodecContext);avformat_seek_file(this->avFormatContext, -1, INT64_MIN, rel, INT64_MAX, 0);}}
评论