要實現(xiàn)的功能:FFmpeg在C++子線程中解碼音頻數(shù)據(jù),得到數(shù)據(jù)包AVPacket。
AVPacket:存放原始音頻/視頻的壓縮包

image.png

image.png

image.png
代碼實現(xiàn):我們定義兩個類HFFmpeg和HAudio分別用于實現(xiàn)解碼相關操作和存放解碼后的音頻的一些相關信息
先看個問題,在寫代碼的過程中發(fā)現(xiàn)的
仔細比較這兩種使用多線程的代碼的區(qū)別
主要就是pthread_t *pthread這種寫法只是定義了一個存放pthread_t的地址的指針,并沒有分配存放pthread_t的內(nèi)存空間

image.png

image.png
代碼實現(xiàn)
目錄結(jié)構(gòu)

image.png
Log.h
日志相關,方便后面多個cpp使用
//
// Created by 霍振鵬 on 2018/10/19.
//
#ifndef VOICEPLAYER_LOG_H
#define VOICEPLAYER_LOG_H
#endif //VOICEPLAYER_LOG_H
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"VoicePlayer",FORMAT,##__VA_ARGS__);
//打印日志方便
#define LOG_DEBUG true
HAudio.h
//
// Created by 霍振鵬 on 2018/10/19.
//
#ifndef VOICEPLAYER_HAUDIO_H
#define VOICEPLAYER_HAUDIO_H
extern "C"
{
#include "libavcodec/avcodec.h"
};
class HAudio{
public:
int streamIndex=-1;
AVCodecParameters *avCodecParameters=NULL;
AVCodecContext *avCodecContext=NULL;
public:
HAudio();
~HAudio();
};
#endif //VOICEPLAYER_HAUDIO_H
HAudio.cpp
//
// Created by 霍振鵬 on 2018/10/19.
//
#include "HAudio.h"
HAudio::HAudio() {
}
HAudio::~HAudio() {
}
HFFmpeg.h
//
// Created by 霍振鵬 on 2018/10/19.
//
#include "CallBackJava.h"
#include <pthread.h>
// sleep 的頭文件
#include <unistd.h>
#include "Log.h"
#include "HAudio.h"
extern "C"
{
#include <libavformat/avformat.h>
}
#ifndef VOICEPLAYER_HFFMPEG_H
#define VOICEPLAYER_HFFMPEG_H
#endif //VOICEPLAYER_HFFMPEG_H
class HFFmpeg{
public:
//記得全部置為NULL
//需要解碼的音頻文件的地址
const char* url=NULL;
//一般可能都需要回調(diào)Java層代碼
CallBackJava *callBackJava=NULL;
//解碼線程
pthread_t pthread_decode=NULL;
AVFormatContext *avFormatContext=NULL;
HAudio *hAudio=NULL;
public:
HFFmpeg(const char* url,CallBackJava *callBackJava);
/**
* 準備解碼
*/
void prepare();
/**
* 開始解碼
*/
void start();
/**
* 解碼線程回調(diào)方法
*/
void decode();
~HFFmpeg();
};
HFFmpeg.cpp
//
// Created by 霍振鵬 on 2018/10/19.
//
#include "HFFmpeg.h"
#include "HAudio.h"
HFFmpeg::~HFFmpeg() {
}
HFFmpeg::HFFmpeg(const char *url, CallBackJava *callBackJava) {
this->url=url;
this->callBackJava=callBackJava;
}
void * decodeFFmpeg(void * data)
{
HFFmpeg *hfFmpeg= (HFFmpeg *) data;
hfFmpeg->decode();
pthread_exit(&hfFmpeg->pthread_decode);
}
void HFFmpeg::prepare() {
//初始化線程,開始解碼
pthread_create(&pthread_decode,NULL,decodeFFmpeg,this);
}
void HFFmpeg::start() {
}
void HFFmpeg::decode() {
av_register_all();
avformat_network_init();
//打開本地文件或者網(wǎng)絡流
avFormatContext=avformat_alloc_context();
//0 on success
int result=0;
result=avformat_open_input(&avFormatContext,url,NULL,NULL);
if(result!=0)
{
if(LOG_DEBUG)
{
LOGI("打開媒體文件失敗");
}
return ;
}
//查找流信息return >=0 if OK
result=avformat_find_stream_info(avFormatContext,NULL);
if(result<0)
{
if(LOG_DEBUG)
{
LOGI("can not find streams from %s", url);
}
return ;
}
//查找音頻流的索引
for(int i=0;i<avFormatContext->nb_streams;i++)
{
if(avFormatContext->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
{
if(hAudio==NULL)
{
hAudio=new HAudio();
hAudio->streamIndex=i;
hAudio->avCodecParameters=avFormatContext->streams[i]->codecpar;
}
}
}
//獲取音頻解碼器
AVCodec *avCodec=avcodec_find_decoder(hAudio->avCodecParameters->codec_id);
if(avCodec==NULL)
{
if(LOG_DEBUG)
{
LOGI("獲取音頻解碼器失敗")
}
return ;
}
//創(chuàng)建解碼器上下文
hAudio->avCodecContext=avcodec_alloc_context3(avCodec);
if(hAudio->avCodecContext==NULL)
{
if(LOG_DEBUG)
{
LOGI("創(chuàng)建解碼器上下文失敗");
}
return ;
}
//將音頻流信息拷貝到新的AVCodecContext結(jié)構(gòu)體中 return >= 0 on success
if(avcodec_parameters_to_context(hAudio->avCodecContext,hAudio->avCodecParameters)<0)
{
if(LOG_DEBUG)
{
LOGI("拷貝失敗");
}
return ;
}
//打開解碼器 zero on success
if(avcodec_open2(hAudio->avCodecContext,avCodec,NULL)!=0)
{
if(LOG_DEBUG)
{
LOGI("打開解碼器失敗");
}
return ;
}
const char* msg="解碼初始化完成";
callBackJava->onPrepared(0,msg);
}
CallBackJava.h
//
// Created by 霍振鵬 on 2018/10/19.
//
#include "jni.h"
#ifndef VOICEPLAYER_CALLBACKJAVA_H
#define VOICEPLAYER_CALLBACKJAVA_H
class CallBackJava
{
public:
JavaVM *javaVM;
JNIEnv *jniEnv;
jobject instance;
jmethodID jmd;
//解碼初始化完成的回調(diào)
jmethodID jmd_prepared;
public:
CallBackJava(JavaVM *vm,JNIEnv *env,jobject job);
~CallBackJava();
/**
* @param type 1主線程,0子線程
* @param code
* @param msg
*/
void onError(int type,int code, const char *msg);
void onPrepared(int type,const char *msg);
};
#endif //VOICEPLAYER_CALLBACKJAVA_H
CallBackJava.cpp
//
// Created by 霍振鵬 on 2018/10/19.
//
#include "CallBackJava.h"
#include "Log.h"
CallBackJava::CallBackJava(JavaVM *vm, JNIEnv *env, jobject job) {
javaVM=vm;
jniEnv=env;
instance=job;
jclass jcl=env->GetObjectClass(job);
this->jmd=env->GetMethodID(jcl,"onError","(ILjava/lang/String;)V");
this->jmd_prepared=env->GetMethodID(jcl,"onPrepared","(Ljava/lang/String;)V");
}
CallBackJava::~CallBackJava() {
LOGI("析構(gòu)函數(shù)執(zhí)行了");
}
void CallBackJava::onError(int type, int code, const char *msg) {
if(type==0)
{
//子線程
//這兒會重新給JNIEnv賦值
javaVM->AttachCurrentThread(&jniEnv,0);
jstring jsr=jniEnv->NewStringUTF(msg);
jniEnv->CallVoidMethod(instance,jmd,code,jsr);
jniEnv->DeleteLocalRef(jsr);
javaVM->DetachCurrentThread();
}
else if(type==1)
{
jstring jsr=jniEnv->NewStringUTF(msg);
//主線程
jniEnv->CallVoidMethod(instance,jmd,code,jsr);
jniEnv->DeleteLocalRef(jsr);
}
}
void CallBackJava::onPrepared(int type,const char *msg) {
if(type==0)
{
//子線程
//這兒會重新給JNIEnv賦值
javaVM->AttachCurrentThread(&jniEnv,0);
jstring jsr=jniEnv->NewStringUTF(msg);
jniEnv->CallVoidMethod(instance,jmd_prepared,jsr);
jniEnv->DeleteLocalRef(jsr);
javaVM->DetachCurrentThread();
}
else if(type==1)
{
jstring jsr=jniEnv->NewStringUTF(msg);
//主線程
jniEnv->CallVoidMethod(instance,jmd_prepared,jsr);
jniEnv->DeleteLocalRef(jsr);
}
}
native-lib.cpp
#include <jni.h>
#include <string>
#include "CallBackJava.h"
#include "Log.h"
#include "HFFmpeg.h"
JavaVM *javaVM;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved)
{
JNIEnv *env;
javaVM = vm;
if(vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
{
return -1;
}
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL JNI_OnUnload (JavaVM *vm, void* reserved)
{
}
CallBackJava *callBackJava=NULL;
HFFmpeg *hfFmpeg=NULL;
/**
* 解碼為AVPacket
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_example_voicelib_Player_startDecode(JNIEnv *env, jobject instance, jstring path_) {
const char *path = env->GetStringUTFChars(path_, 0);
if(hfFmpeg==NULL)
{
if(callBackJava==NULL)
{
callBackJava=new CallBackJava(javaVM,env,env->NewGlobalRef(instance));
}
hfFmpeg=new HFFmpeg(path,callBackJava);
hfFmpeg->prepare();
}
// env->ReleaseStringUTFChars(path_, path);
}
Player.java
package com.example.voicelib;
import android.util.Log;
/**
* 作者 huozhenpeng
* 日期 2018/10/18
* 郵箱 huohacker@sina.com
*/
public class Player {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("postproc-54");
System.loadLibrary("avfilter-6");
System.loadLibrary("avdevice-57");
}
public void onError(int code,String msg)
{
Log.e("VoicePlayer","code:"+code+"msg:"+msg);
}
public void onPrepared(String msg)
{
Log.e("VoicePlayer","[Java]msg:"+msg);
}
/**
* 開始解碼
*/
public native void startDecode(String path);
}
調(diào)用:
public void startDecode(View view) {
Player player=new Player();
player.startDecode(Environment.getExternalStorageDirectory()+"/lame.mp3");
}
輸出結(jié)果:
image.png