要实现的功能:FFmpeg在C++子线程中解码音频数据,得到数据包AVPacket。
AVPacket:存放原始音频/视频的压缩包
代码实现:我们定义两个类HFFmpeg和HAudio分别用于实现解码相关操作和存放解码后的音频的一些相关信息
先看个问题,在写代码的过程中发现的
仔细比较这两种使用多线程的代码的区别
主要就是pthread_t *pthread这种写法只是定义了一个存放pthread_t的地址的指针,并没有分配存放pthread_t的内存空间
代码实现
目录结构
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;
//一般可能都需要回调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();
/**
* 解码线程回调方法
*/
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();
//打开本地文件或者网络流
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 ;
}
//创建解码器上下文
hAudio->avCodecContext=avcodec_alloc_context3(avCodec);
if(hAudio->avCodecContext==NULL)
{
if(LOG_DEBUG)
{
LOGI("创建解码器上下文失败");
}
return ;
}
//将音频流信息拷贝到新的AVCodecContext结构体中 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;
//解码初始化完成的回调
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("析构函数执行了");
}
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);
}
调用:
public void startDecode(View view) {
Player player=new Player();
player.startDecode(Environment.getExternalStorageDirectory()+"/lame.mp3");
}
输出结果: