1、FFmpeg解碼流程(圖解)
2、FFmpeg解碼流程(代碼)
3弟蚀、實(shí)現(xiàn)步驟
- 注冊(cè)解碼器并初始化網(wǎng)絡(luò)
av_register_all();
avformat_network_init();
- 打開(kāi)文件或網(wǎng)絡(luò)流
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);
- 利用解碼器創(chuàng)建解碼器上下文
AVCodecContext *avCodecContext = avcodec_alloc_context3(dec);
avcodec_parameters_to_context(audio->avCodecContext, audio->codecpar)
- 打開(kāi)解碼器
avcodec_open2(audio->avCodecContext, dec, 0)
- 讀取音頻幀
AVPacket *packet = av_packet_alloc();
av_read_frame(pFormatCtx, packet);
4、代碼結(jié)構(gòu)
4.1熙含、做解碼前的準(zhǔn)備
解碼前的準(zhǔn)備工作是在C++層做的,所以設(shè)置一個(gè)準(zhǔn)備工作完成以后的回調(diào)方法艇纺,其實(shí)是由C++層通知Java層:
public interface JfOnPreparedListener {
void onPrepared();
}
所以要為C++層提供一個(gè)方法來(lái)回調(diào)上面的方法:
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(){
}
/**
* 設(shè)置數(shù)據(jù)源
* @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()完成后要調(diào)用JfOnPreparedListener
*/
public void onCallPrepared(){
if (jfOnPreparedListener != null)
{
jfOnPreparedListener.onPrepared();
}
}
public native void n_prepared(String source);
}
因?yàn)镃++層要調(diào)用Java層代碼,所以先在C++層實(shí)現(xiàn)這個(gè)功能邮弹,新建一個(gè)C++類(lèi)-JfCallJava:
JfCallJava.h
#define MAIN_THREAD 0
#define CHILD_THREAD 1
/**
* C++層調(diào)用Java層的類(lèi)
*/
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);//這里調(diào)用Java層的onCallPrepare方法黔衡,因?yàn)榭赡茉谥骶€(xiàn)程或者子線(xiàn)程中調(diào)用,所以加了這個(gè)方法
};
JfCallJava.cpp
#include "JfCallJava.h"
JfCallJava::JfCallJava(JavaVM *vm, JNIEnv *env, jobject *obj) {
this->javaVM = vm;
this->jniEnv = env;
this->jobj = env->NewGlobalRef(*obj);//設(shè)為全局
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();
}
}
編碼的過(guò)程由FFmpeg在一個(gè)子線(xiàn)程中完成腌乡,創(chuàng)建一個(gè)C++類(lèi)-JfFFmpeg盟劫,將source的路徑傳進(jìn)去
JfFFmpeg.h
class JfFFmpeg {
public:
JfCallJava *callJava = NULL;//初始化回調(diào)java方法封裝
const char *url = NULL;//文件的url
pthread_t decodeThread = NULL;//解碼的子線(xiàn)程
/**
* 解碼相關(guān)
*/
AVFormatContext *pAFmtCtx = NULL; //封裝上下文
JfAudio *audio = NULL;//封裝Audio信息
public:
JfFFmpeg(JfCallJava *callJava,const char *url);//參數(shù)都是從外面?zhèn)鬟M(jìn)來(lái)的
~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);//退出線(xiàn)程
}
/**
* 正式解碼的過(guò)程,開(kāi)一個(gè)子線(xiàn)程解碼
*/
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)){//將解碼器中信息復(fù)制到上下文當(dāng)中
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);
}
創(chuàng)建一個(gè)C++類(lèi)-JfAudio与纽,保存音頻解碼過(guò)程中要用到的參數(shù):
JfAudio.h
class JfAudio {
public:
int streamIndex = -1;//stream索引
AVCodecParameters *codecpar = NULL;//包含音視頻參數(shù)的結(jié)構(gòu)體侣签。很重要,可以用來(lái)獲取音視頻參數(shù)中的寬度急迂、高度影所、采樣率、編碼格式等信息僚碎。
AVCodecContext *pACodecCtx = NULL;
public:
JfAudio();
~JfAudio();
};
JfAudio.cpp
#include "JfAudio.h"
JfAudio::JfAudio() {
}
JfAudio::~JfAudio() {
}
到這里猴娩,我們已經(jīng)完成了所有的準(zhǔn)備工作,接下來(lái)就要開(kāi)始讀取音頻幀
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++層去實(shí)現(xiàn)native方法:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myplayer_player_JfPlayer_n_1start(JNIEnv *env, jobject instance) {
// TODO
if (ffmpeg != NULL){
ffmpeg->start();
}
}
實(shí)現(xiàn)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;
}
}
}
這里每次取出一幀都要釋放緩存勺阐。