Android 万能音频播放器一 C++多线程解码音频数据
目录
- Android 万能音频播放器 一 C++多线程解码音频数据
- Android 万能音频播放器 二 C++队列存放AvPacket
- OpenSL介绍 并实现播放PCM功能
前言
如果对Android NDK 下的C++ 多线程不了解的话可以看这篇文章Android NDK 多线程 本系列将用到Android NDK,C++,FFmpeg等相关知识,努力做好SDK的封装,方便以后在项目中可以复用。
本章实现
FFmpeg在C++子线程中解码音频数据,得到数据包AVPacket
AVPacket:存放原始音频/视频的压缩包
实现步骤
- 注册解码器并初始化网络
- 打开文件或网络流
- 获取流信息
- 获取音频流
- 获取解码器
- 打开解码器
- 读取音频帧
FFmpeg解码流程(代码)
运行截图
代碼結構
目录结构
音频解码信息封装
//音频解码所需信息封装
class WIAudio {
public:
//音頻流索引浩
int audio_stream_index;
//音頻流信息
AVCodecParameters *av_codec_par = NULL;
//音频解码器上下文
AVCodecContext * av_codec_ctx = NULL;
WIAudio();
~WIAudio();
};
回调java接口封装
class WIPreparedListener {
private:
//java虚拟机类 用来获取JNIEnv对象 因为每个线程都有自己的env
JavaVM *javaVM = NULL;
//JNI环境对象
JNIEnv * env = NULL;
//回调对象 一般方法用jobject 静态方法用jclass
jobject listener_obj;
//回调方法的id
jmethodID prepare_mid;
public:
WIPreparedListener(JavaVM *javaVM,JNIEnv * env,jobject listener_obj);
~WIPreparedListener();
void onPrepared(int type);
};
音频解码封装
class WIPlayer {
public:
//音频解码所需属性封装
WIAudio *audio = NULL;
//初始化回调java方法封装
WIPreparedListener * listener;
//播放URL
const char * url = NULL;
//封装上下文
AVFormatContext * av_format_ctx = NULL;
//解码线程标识
pthread_t decodeThread;
public:
WIPlayer(const char * url,WIPreparedListener * listener);
~WIPlayer();
void prepare();
void audioDecodeThread();
void start();
};
解码具体实现
#include "WIPlayer.h"
WIPlayer::WIPlayer(const char *url, WIPreparedListener *listener) {
this->url = url;
this->listener = listener;
}
WIPlayer::~WIPlayer() {
}
void *decodeFFmpeg(void *data) {
//强制类型转换
WIPlayer *player = (WIPlayer *) data;
player->audioDecodeThread();
pthread_exit((void *) player->decodeThread);
}
void WIPlayer::prepare() {
//创建子线程 并将本身传入
pthread_create(&decodeThread, NULL, decodeFFmpeg, this);
}
void WIPlayer::audioDecodeThread() {
//注册所有组件并初始化网络
av_register_all();
avformat_network_init();
av_format_ctx = avformat_alloc_context();
//打开文件或网络流
if (avformat_open_input(&av_format_ctx, url, NULL, NULL) != 0) {
LOGE("can not open url :%s", url);
return;
}
//获取流信息
if (avformat_find_stream_info(av_format_ctx, NULL) < 0) {
LOGE("can not find streams from %s", url);
return;
}
//根据codecpar->codec_type获取音频流索引和音频流信息 此版本不能用streams[i]->codec来直接获取解码上下文
for (int i = 0; i < av_format_ctx->nb_streams; ++i) {
if (av_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
if (audio == NULL) {
audio = new WIAudio();
audio->audio_stream_index = i;
audio->av_codec_par = av_format_ctx->streams[i]->codecpar;
}
}
}
//获取解码器
AVCodec *decoder = avcodec_find_decoder(audio->av_codec_par->codec_id);
if (decoder == NULL) {
LOGE("can not find decoder from %s", url);
return;
}
//申请解码上下文
audio->av_codec_ctx = avcodec_alloc_context3(decoder);
if (!audio->av_codec_ctx) {
LOGE("can not alloc accodecxtx");
return;
}
//将音频流信息填充到解码上下文
if (avcodec_parameters_to_context(audio->av_codec_ctx, audio->av_codec_par) < 0) {
LOGE("can not parameters to context");
return;
}
//打开解码器
if (avcodec_open2(audio->av_codec_ctx, decoder, 0) != 0) {
LOGE("can not open decoder from %s", url);
return;
}
if (listener != NULL) {
listener->onPrepared(CHILD_THREAD);
}
}
void WIPlayer::start() {
if (audio == NULL) {
LOGE("audio is empty");
return;
}
int count = 0;
//开始解码
while (1) {
//新api提供了avpacket的申请方式 。此语句不能再while外面 因为申请的已经释放,必须重新申请
AVPacket *avPacket = av_packet_alloc();
if (av_read_frame(av_format_ctx, avPacket) == 0) {
if (avPacket->stream_index == audio->audio_stream_index) {
count++;
LOGE("解码第%d帧", count);
av_packet_free(&avPacket);
av_free(avPacket);
} else {
//释放avpacket
av_packet_free(&avPacket);
av_free(avPacket);
}
} else {
LOGE("解码结束共%d帧", count);
//释放avpacket
av_packet_free(&avPacket);
av_free(avPacket);
break;
}
}
}
源码地址
更多推荐
Android 万能音频播放器 一 C++多线程解码音频数据
发布评论