前言
代碼github地址https://github.com/ccj659/NDK-FFmpeg-master
FFmpeg庫簡介
- avcodec:編解碼,包含
- avformate: 封裝格式處理
- avfilter:濾鏡特效處理
- avdevice:輸入輸出設(shè)備
- avutil:工具庫
- swresample:音頻采樣處理
- swscale:視頻像素格式轉(zhuǎn)換,縮放等
FFmpeg解碼流程
- av_register_all(); //注冊所有組件。
- AVFormatContext //獲取上下文等信息//是封裝格式上下文結(jié)構(gòu)體,統(tǒng)領(lǐng)全局,保存視頻文件的封裝格式信息
- avformat_open_input(&pFormCtx,input_cstr,NULL,NULL) //打開輸入文件
- avformat_find_stream_info(pFormCtx,NULL)////獲取文件信息,
- AVStream,AVCodecContext獲取流索引位置
- avcodec_find_decoder(avCodeCtx->codec_id)////查找解碼器。
- avcodec_open2(avCodeCtx,avCode,NULL)//打開解碼器。
- AVPacket,AVFrame//獲取幀數(shù)據(jù),申請空間
- av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height))//緩沖區(qū)分配內(nèi)存
- avpicture_fill((AVPicture *)yuvFrame, out_buffer, AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height);//初始化緩沖區(qū)
- while(av_read_frame(pFormatCtx,packet)//一陣一陣讀取壓縮的視頻數(shù)據(jù)AVPacket
- avcodec_decode_video2(pCodeCtx, frame, &got_frame, packet)//解碼AVPacket->AVFrame
- sws_scale//轉(zhuǎn)為指定的YUV420P像素幀
- fwrite(yuvFrame->data[0], 1, y_size, fp_yuv);/向YUV文件保存解碼之后的幀數(shù)據(jù)//AVFrame->YUV//一個像素包含一個Y
- 釋放相關(guān)內(nèi)存av_free_packet,fclose,av_frame_free,avcodec_close...

FFmpeg解碼的數(shù)據(jù)結(jié)構(gòu)(主要的namespace)
-
AVFormatContext 封裝格式上下文結(jié)構(gòu)體,也是統(tǒng)領(lǐng)全局的結(jié)構(gòu)體,保存了視頻文件封裝
格式相關(guān)信息。- iformat:輸入視頻的AVInputFormat
- nb_streams :輸入視頻的AVStream 個數(shù)
- streams :輸入視頻的AVStream []數(shù)組
- duration :輸入視頻的時長(以微秒為單位)
- bit_rate :輸入視頻的碼率
-
AVInputFormat 每種封裝格式(例如FLV, MKV, MP4, AVI)對應(yīng)一個該結(jié)構(gòu)體。
- name:封裝格式名稱
- long_name:封裝格式的長名稱
- extensions:封裝格式的擴(kuò)展名
- id:封裝格式ID
- 一些封裝格式處理的接口函數(shù)
- FFmpeg數(shù)據(jù)結(jié)構(gòu)分析
-
AVStream 視頻文件中每個視頻(音頻)流對應(yīng)一個該結(jié)構(gòu)體。
- id:序號
- codec:該流對應(yīng)的AVCodecContext
- time_base:該流的時基
- r_frame_rate:該流的幀率
-
AVCodecContext編碼器上下文結(jié)構(gòu)體,保存了視頻(音頻)編解碼相關(guān)信息。
- codec:編解碼器的AVCodec
- width, height:圖像的寬高(只針對視頻)
- pix_fmt:像素格式(只針對視頻)
- sample_rate:采樣率(只針對音頻)
- channels:聲道數(shù)(只針對音頻)
- sample_fmt:采樣格式(只針對音頻)
-
AVCodec 每種視頻(音頻)編解碼器(例如H.264解碼器)對應(yīng)一個該結(jié)構(gòu)體。
- name:編解碼器名稱
- long_name:編解碼器長名稱
- type:編解碼器類型
- id:編解碼器ID
- 一些編解碼的接口函數(shù)
-
AVPacket 存儲一幀壓縮編碼數(shù)據(jù)。
- pts:顯示時間戳
- dts :解碼時間戳
- data :壓縮編碼數(shù)據(jù)
- size :壓縮編碼數(shù)據(jù)大小
- stream_index :所屬的AVStream
-
AVFrame存儲一幀解碼后像素(采樣)數(shù)據(jù)。
- data:解碼后的圖像像素數(shù)據(jù)(音頻采樣數(shù)據(jù))。
- linesize:對視頻來說是圖像中一行像素的大??;對音頻來說是音頻幀的大小。
- width, height:圖像的寬高(只針對視頻)。
- key_frame:是否為關(guān)鍵幀(只針對視頻) 。
- pict_type:幀類型(只針對視頻) 。例如I,P,B。
FFmpeg解碼參照
- 開發(fā)過程中可參考 ffmpeg-2.6.9\doc\examples中的例子.
- 在該例子文件夾下中 有音視頻的讀寫, 過濾處理,轉(zhuǎn)碼等相關(guān)例子,大家可以進(jìn)行參考學(xué)習(xí).

FFmpeg的NDK視頻解碼播放實(shí)踐
在本例中 以視頻解碼播放為例,在下手之前,請搞懂 FFmpeg解碼的數(shù)據(jù)結(jié)構(gòu)中的命名空間相關(guān)的屬性以及作用.
1.編譯ffmpeg成so庫
在linux中編譯ffmpeg
1.編寫shell腳本文件
2.配置configuration
3.給文件權(quán)限:chmod 777 android_build.sh
4.執(zhí)行 ./android_build.sh

2.編譯libyuv庫
1.下載libyuv的庫,大家克隆即可https://chromium.googlesource.com/external/libyuv
2.執(zhí)行ndk-build(linux需要提前配置ndk環(huán)境)
3.新建工程,導(dǎo)入庫

4.配置工程環(huán)境
1.add native support.請參考 eclipse搭建NDK開發(fā)環(huán)境
2.配置Android.mk application.mk 請參考項(xiàng)目ffmpeg-palyer
3.編寫本地方法,并實(shí)現(xiàn)C代碼,生成so.
1.用自定義的SurfaceView 作為視頻渲染載體.
2.編寫本地方法,實(shí)現(xiàn)代碼.
public class CcjPlayer {
//視頻
public native void render(String input,Object surface);
//音頻
public native void sound(String input,String output);
//注意加載庫的順序
static{
System.loadLibrary("avutil-54");
System.loadLibrary("swresample-1");
System.loadLibrary("avcodec-56");
System.loadLibrary("avformat-56");
System.loadLibrary("swscale-3");
System.loadLibrary("postproc-53");
System.loadLibrary("avfilter-5");
System.loadLibrary("avdevice-56");
System.loadLibrary("myffmpeg");
}
}
3.生成頭文件,并實(shí)現(xiàn)方法(實(shí)現(xiàn)思路和流程請參考FFmpeg解碼流程).
/*
* Class: com_ccj_ffmpeg_CcjPlayer
* Method: render
* Signature: (Ljava/lang/String;Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL Java_com_ccj_ffmpeg_CcjPlayer_render
(JNIEnv *env, jobject jobj, jstring input_jstr, jobject surface){
const char* input_cstr = (*env)->GetStringUTFChars(env,input_jstr,NULL);
//1.注冊組件
av_register_all();
//封裝格式上下文
AVFormatContext *pFormatCtx = avformat_alloc_context();
//2.打開輸入視頻文件
if(avformat_open_input(&pFormatCtx,input_cstr,NULL,NULL) != 0){
LOGE("%s","打開輸入視頻文件失敗");
return;
}
//3.獲取視頻信息
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGE("%s","獲取視頻信息失敗");
return;
}
//視頻解碼,需要找到視頻對應(yīng)的AVStream所在pFormatCtx->streams的索引位置
int video_stream_idx = -1;
int i = 0;
for(; i < pFormatCtx->nb_streams;i++){
//根據(jù)類型判斷,是否是視頻流
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
video_stream_idx = i;
break;
}
}
//4.獲取視頻解碼器
AVCodecContext *pCodeCtx = pFormatCtx->streams[video_stream_idx]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
if(pCodec == NULL){
LOGE("%s","無法解碼");
return;
}
//5.打開解碼器
if(avcodec_open2(pCodeCtx,pCodec,NULL) < 0){
LOGE("%s","解碼器無法打開");
return;
}
//編碼數(shù)據(jù)
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//像素數(shù)據(jù)(解碼數(shù)據(jù))
AVFrame *yuv_frame = av_frame_alloc();
AVFrame *rgb_frame = av_frame_alloc();
/*//ScaleImg視頻縮放
if(ScaleImg(pCodeCtx,yuv_frame,rgb_frame,pCodeCtx->width/6,pCodeCtx->width/6)!=1){
LOGE("%s","縮放失敗");
return;
}*/
//native繪制
//窗體
ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env,surface);
//繪制時的緩沖區(qū)
ANativeWindow_Buffer outBuffer;
int len ,got_frame, framecount = 0;
//6.一陣一陣讀取壓縮的視頻數(shù)據(jù)AVPacket
while(av_read_frame(pFormatCtx,packet) >= 0){
//解碼AVPacket->AVFrame
len = avcodec_decode_video2(pCodeCtx, yuv_frame, &got_frame, packet);
//Zero if no frame could be decompressed
//非零,正在解碼
if(got_frame){
LOGI("解碼%d幀",framecount++);
//lock
//設(shè)置緩沖區(qū)的屬性(寬、高、像素格式)
ANativeWindow_setBuffersGeometry(nativeWindow, pCodeCtx->width, pCodeCtx->height,WINDOW_FORMAT_RGBA_8888);
ANativeWindow_lock(nativeWindow,&outBuffer,NULL);
//設(shè)置rgb_frame的屬性(像素格式、寬高)和緩沖區(qū)
//rgb_frame緩沖區(qū)與outBuffer.bits是同一塊內(nèi)存
avpicture_fill((AVPicture *)rgb_frame, outBuffer.bits, AV_PIX_FMT_RGBA, pCodeCtx->width, pCodeCtx->height);
//YUV->RGBA_8888
I420ToARGB(yuv_frame->data[0],yuv_frame->linesize[0],
yuv_frame->data[2],yuv_frame->linesize[2],
yuv_frame->data[1],yuv_frame->linesize[1],
rgb_frame->data[0], rgb_frame->linesize[0],
pCodeCtx->width,pCodeCtx->height);
//unlock
ANativeWindow_unlockAndPost(nativeWindow);
usleep(1000 * 16);
}
av_free_packet(packet);
}
ANativeWindow_release(nativeWindow);
av_frame_free(&yuv_frame);
avcodec_close(pCodeCtx);
avformat_free_context(pFormatCtx);
(*env)->ReleaseStringUTFChars(env,input_jstr,input_cstr);
}
3.生成頭文件,并實(shí)現(xiàn)方法(實(shí)現(xiàn)思路和流程請參考FFmpeg解碼流程).
/**
* 萬能解碼器,播放視頻器
* @author ccj
*
*/
public class MainActivity extends Activity {
private CcjPlayer player;
private VideoView videoView;
private Spinner sp_video;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
videoView = (VideoView) findViewById(R.id.video_view);
sp_video = (Spinner) findViewById(R.id.sp_video);
player = new CcjPlayer();
//選擇 <item>input.mp4</item> <item>音頻</item>
String[] videoArray = getResources().getStringArray(R.array.video_list);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
android.R.id.text1, videoArray);
sp_video.setAdapter(adapter);
}
public void mPlay(View btn){
String video = sp_video.getSelectedItem().toString();
if("音頻".equals(video)){
String input = new File(Environment.getExternalStorageDirectory(),"Live.mp3").getAbsolutePath();
String output = new File(Environment.getExternalStorageDirectory(),"Live.pcm").getAbsolutePath();
player.sound(input, output);
}else{
String input = new File(Environment.getExternalStorageDirectory(),video).getAbsolutePath();
//Surface傳入到Native函數(shù)中,用于繪制
Surface surface = videoView.getHolder().getSurface();
player.render(input, surface);
}
}
}
4. 導(dǎo)入input.mp4到指定位置.播放即可.
log信息
解碼器播放效果
4.不足和待改進(jìn)
1.由于繪制過程在主線程,所以會出現(xiàn)ANR,后期再處理
2.本項(xiàng)目是學(xué)習(xí)的一次實(shí)踐,后期會繼續(xù)補(bǔ)充
總結(jié)
目前市面上的音視頻解碼播放器,大多都是基于ffmpeg的代碼的,例如騰訊影音,暴風(fēng)影音等等.在此,感謝jason老師,致敬ffmpeg雷霄驊的博客.
路漫漫其修遠(yuǎn)兮,吾將上下而求索.
關(guān)于我
1.github地址https://github.com/ccj659/NDK-FFmpeg-master
2.CSDN博客http://blog.csdn.net/ccj659/
3.簡書地址http://m.itdecent.cn/users/94423b4ef5cf/latest_articles
4.QQ : 569948231

