1、FFmpeg解码流程(图解)
2、FFmpeg解码流程(代码)
3、实现步骤
- 注册解码器并初始化网络
av_register_all();
avformat_network_init();
- 打开文件或网络流
AVFormatContext *pFormatCtx = avformat_alloc_context();
avformat_open_input(&pFormatCtx, url, NULL, NULL)
- 获取流信息
avformat_find_stream_info(pFormatCtx, NULL)
- 获取音频流
pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO
- 获取解码器
AVCodec *dec = avcodec_find_decoder(audio->codecpar->codec_id);
- 利用解码器创建解码器上下文
AVCodecContext *avCodecContext = avcodec_alloc_context3(dec);
avcodec_parameters_to_context(audio->avCodecContext, audio->codecpar)
- 打开解码器
avcodec_open2(audio->avCodecContext, dec, 0)
- 读取音频帧
AVPacket *packet = av_packet_alloc();
av_read_frame(pFormatCtx, packet);
4、代码结构
4.1、做解码前的准备
解码前的准备工作是在C++层做的,所以设置一个准备工作完成以后的回调方法,其实是由C++层通知Java层:
public interface JfOnPreparedListener {
void onPrepared();
}
所以要为C++层提供一个方法来回调上面的方法:
public void onCallPrepared(){
if (jfOnPreparedListener != null)
{
jfOnPreparedListener.onPrepared();
}
}
Java层代码:
public class JfPlayer {
static {
System.loadLibrary("avutil-55");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("avdevice-57");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");
System.loadLibrary("postproc-54");
System.loadLibrary("avfilter-6");
System.loadLibrary("native-lib");
}
private String source;
private JfOnPreparedListener jfOnPreparedListener;
public JfPlayer(){
}
/**
* 设置数据源
* @param source
*/
public void setSource(String source) {
this.source = source;
}
public void setJfOnPreparedListener(JfOnPreparedListener jfOnPreparedListener) {
this.jfOnPreparedListener = jfOnPreparedListener;
}
public void prepared(){
if (TextUtils.isEmpty(source)){
JfLog.w("SOURCE IS EMPTY");
return;
}
new Thread(new Runnable() {
@Override
public void run() {
n_prepared(source);
}
}).start();
}
/**
* C++层n_prepare()完成后要调用JfOnPreparedListener
*/
public void onCallPrepared(){
if (jfOnPreparedListener != null)
{
jfOnPreparedListener.onPrepared();
}
}
public native void n_prepared(String source);
}
因为C++层要调用Java层代码,所以先在C++层实现这个功能,新建一个C++类-JfCallJava:
JfCallJava.h
#define MAIN_THREAD 0
#define CHILD_THREAD 1
/**
* C++层调用Java层的类
*/
class JfCallJava {
public:
JavaVM *javaVM = NULL;
JNIEnv *jniEnv = NULL;
jobject jobj;
jmethodID jmid_prepared;
public:
JfCallJava(JavaVM *vm,JNIEnv *env,jobject *obj);
~JfCallJava();
void onCallPrepared(int threadType);//这里调用Java层的onCallPrepare方法,因为可能在主线程或者子线程中调用,所以加了这个方法
};
JfCallJava.cpp
#include "JfCallJava.h"
JfCallJava::JfCallJava(JavaVM *vm, JNIEnv *env, jobject *obj) {
this->javaVM = vm;
this->jniEnv = env;
this->jobj = env->NewGlobalRef(*obj);//设为全局
jclass jclz = env->GetObjectClass(jobj);
if (!jclz) {
LOGE("get jclass error");
return ;
}
jmid_prepared = env->GetMethodID(jclz,"onCallPrepared","()V");
}
JfCallJava::~JfCallJava() {
}
void JfCallJava::onCallPrepared(int threadType) {
if (threadType == MAIN_THREAD){
jniEnv->CallVoidMethod(jobj,jmid_prepared);
} else if (threadType == CHILD_THREAD){
JNIEnv *jniEnv;
if (javaVM->AttachCurrentThread(&jniEnv,0) != JNI_OK){
if (LOG_DEBUG) {
LOGE("GET CHILD THREAD JNIENV ERROR");
return;
}
}
jniEnv->CallVoidMethod(jobj,jmid_prepared);
javaVM->DetachCurrentThread();
}
}
编码的过程由FFmpeg在一个子线程中完成,创建一个C++类-JfFFmpeg,将source的路径传进去
JfFFmpeg.h
class JfFFmpeg {
public:
JfCallJava *callJava = NULL;//初始化回调java方法封装
const char *url = NULL;//文件的url
pthread_t decodeThread = NULL;//解码的子线程
/**
* 解码相关
*/
AVFormatContext *pAFmtCtx = NULL; //封装上下文
JfAudio *audio = NULL;//封装Audio信息
public:
JfFFmpeg(JfCallJava *callJava,const char *url);//参数都是从外面传进来的
~JfFFmpeg();
void prepare();
void decodeAudioThread();
};
JfFFmpeg.cpp
JfFFmpeg::JfFFmpeg(JfCallJava *callJava, const char *url) {
this->callJava = callJava;
this->url = url;
}
void *decodeFFmpeg(void *data){
JfFFmpeg *jfFFmpeg = (JfFFmpeg *)(data);
jfFFmpeg->decodeAudioThread();
pthread_exit(&jfFFmpeg->decodeThread);//退出线程
}
/**
* 正式解码的过程,开一个子线程解码
*/
void JfFFmpeg::prepare() {
pthread_create(&decodeThread,NULL,decodeFFmpeg,this);
}
void JfFFmpeg::decodeAudioThread() {
av_register_all();
avformat_network_init();
pAFmtCtx = avformat_alloc_context();
if (avformat_open_input(&pAFmtCtx,url,NULL,NULL) != 0){
if (LOG_DEBUG){
LOGE("open url file error url === %s",url);
}
return;
}
if (avformat_find_stream_info(pAFmtCtx,NULL) < 0){
if (LOG_DEBUG){
LOGE("find stream info error url === %s",url);
}
return;
}
for (int i = 0; i < pAFmtCtx->nb_streams; i++) {
if (pAFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
if (audio == NULL) {
audio = new JfAudio;
audio->streamIndex = i;
audio->codecpar = pAFmtCtx->streams[i]->codecpar;
}
}
}
AVCodec *dec = avcodec_find_decoder(audio->codecpar->codec_id);
if (!dec){
if (LOG_DEBUG){
LOGE("FIND DECODER ERROR");
}
return;
}
audio->pACodecCtx = avcodec_alloc_context3(dec);
if (!audio->pACodecCtx){
if (LOG_DEBUG){
LOGE("avcodec_alloc_context3 ERROR");
}
return;
}
if (avcodec_parameters_to_context(audio->pACodecCtx,audio->codecpar)){//将解码器中信息复制到上下文当中
if (LOG_DEBUG){
LOGE("avcodec_parameters_to_context ERROR");
}
return;
}
if (avcodec_open2(audio->pACodecCtx,dec,NULL) < 0){
if (LOG_DEBUG){
LOGE("avcodec_open2 ERROR");
}
return;
}
callJava->onCallPrepared(CHILD_THREAD);
}
创建一个C++类-JfAudio,保存音频解码过程中要用到的参数:
JfAudio.h
class JfAudio {
public:
int streamIndex = -1;//stream索引
AVCodecParameters *codecpar = NULL;//包含音视频参数的结构体。很重要,可以用来获取音视频参数中的宽度、高度、采样率、编码格式等信息。
AVCodecContext *pACodecCtx = NULL;
public:
JfAudio();
~JfAudio();
};
JfAudio.cpp
#include "JfAudio.h"
JfAudio::JfAudio() {
}
JfAudio::~JfAudio() {
}
到这里,我们已经完成了所有的准备工作,接下来就要开始读取音频帧
Java层添加native方法:
public void start(){
if (TextUtils.isEmpty(source)){
JfLog.w("SOURCE IS EMPTY");
return;
}
new Thread(new Runnable() {
@Override
public void run() {
n_start();
}
}).start();
}
public native void n_start();
C++层去实现native方法:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myplayer_player_JfPlayer_n_1start(JNIEnv *env, jobject instance) {
// TODO
if (ffmpeg != NULL){
ffmpeg->start();
}
}
实现start方法:
void JfFFmpeg::start() {
if (audio == NULL) {
if (LOG_DEBUG){
LOGE("AUDIO == NULL");
}
}
int count;
while (1) {
AVPacket *avPacket = av_packet_alloc();
if (av_read_frame(pAFmtCtx,avPacket) == 0) {
if (avPacket->stream_index == audio->streamIndex){
count++;
if (LOG_DEBUG) {
LOGD("解码第%d帧",count);
}
av_packet_free(&avPacket);
av_free(avPacket);
avPacket = NULL;
} else {
av_packet_free(&avPacket);
av_free(avPacket);
avPacket = NULL;
}
} else {
av_packet_free(&avPacket);
av_free(avPacket);
avPacket = NULL;
}
}
}
这里每次取出一帧都要释放缓存。