若是你已經準備好ffmpeg的開發環境,那麼咱們在這篇文章中實現對視頻的一個播放,若是尚未準備好,請看前面的內容。java
Ok,上圖就是使用ffmpeg實現了一個視頻的播放的大概流程圖,那麼,咱們將根據流程圖來編寫代碼,這樣子,代碼的編寫就會顯得比較簡單,比較好理解了。
1.註冊各大組件,這一步很重要,若是不註冊就沒法使用後面的函數了。緩存
av_register_all();
2.在解碼以前咱們得獲取裏面的內容吧,因此這一步就是打開地址而且獲取裏面的內容。其中avFormatContext是內容的一個上下文,inputPath爲輸入的地址。ide
AVFormatContext *avFormatContext = avformat_alloc_context();//獲取上下文 avformat_open_input(&avFormatContext, inputPath, NULL, NULL)//解封裝 avformat_find_stream_info(avFormatContext, NULL)
3.咱們在上面已經獲取了內容,可是在一個音視頻中包括了音頻流,視頻流和字幕流,因此在全部的內容當中,咱們應當找出相對應的視頻流。函數
int video_index=-1; for (int i = 0; i < avFormatContext->nb_streams; ++i) { if (avFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { //若是是視頻流,標記一哈 video_index = i; } }
4.在第三步的時候已經找到了視頻流,那麼咱們就對視頻流進行解碼、轉換和繪製。
a.若是要進行解碼,那麼得有解碼的裝置並打開解碼的裝置。post
//獲取解碼的裝置上下文 AVCodecContext *avCodecContext = avFormatContext->streams[video_index]->codec; //獲取解碼的裝置 AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id); //打開解碼的裝置 if (avcodec_open2(avCodecContext, avCodec, NULL) < 0) { LOGE("打開失敗") return; }
b.申請AVPacket和AVFrame,其中AVPacket的做用是:保存解碼以前的數據和一些附加信息,如顯示時間戳(pts)、解碼時間戳(dts)、數據時長,所在媒體流的索引等;AVFrame的做用是:存放解碼事後的數據。
具體可參考:http://blog.csdn.net/leixiaohua1020/article/details/11693997測試
//申請AVPacket AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket)); av_init_packet(packet); //申請AVFrame AVFrame *frame = av_frame_alloc();//分配一個AVFrame結構體,AVFrame結構體通常用於存儲原始數據,指向解碼後的原始幀 AVFrame *rgb_frame = av_frame_alloc();//分配一個AVFrame結構體,指向存放轉換成rgb後的幀
c.由於rgb_frame是一個緩存區域,因此須要設置。ui
//緩存區 uint8_t *out_buffer= (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_RGBA, avCodecContext->width,avCodecContext->height)); //與緩存區相關聯,設置rgb_frame緩存區 avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,avCodecContext->width,avCodecContext->height);
d.由於是原生繪製,便是說須要ANativeWindow。this
//取到nativewindow ANativeWindow *nativeWindow=ANativeWindow_fromSurface(env,surface); if(nativeWindow==0){ LOGE("nativewindow取到失敗") return; } //視頻緩衝區 ANativeWindow_Buffer native_outBuffer;
e.一切準備穩當,那麼咱們開始解碼。spa
while (av_read_frame(avFormatContext, packet) >= 0) { LOGE("解碼 %d",packet->stream_index) LOGE("VINDEX %d",video_index) if(packet->stream_index==video_index){ LOGE("解碼 hhhhh") //若是是視頻流 //解碼 avcodec_decode_video2(avCodecContext, frame, &frameCount, packet) } av_free_packet(packet); }
f.如下均在循環裏面進行,當解碼一幀成功事後,咱們轉換成rgb格式而且繪製。.net
if (frameCount) { LOGE("轉換並繪製") //說明有內容 //繪製以前配置nativewindow ANativeWindow_setBuffersGeometry(nativeWindow,avCodecContext->width,avCodecContext->height,WINDOW_FORMAT_RGBA_8888); //上鎖 ANativeWindow_lock(nativeWindow, &native_outBuffer, NULL); //轉換爲rgb格式 sws_scale(swsContext,(const uint8_t *const *)frame->data,frame->linesize,0, frame->height,rgb_frame->data, rgb_frame->linesize); // rgb_frame是有畫面數據 uint8_t *dst= (uint8_t *) native_outBuffer.bits; // 拿到一行有多少個字節 RGBA int destStride=native_outBuffer.stride*4; //像素數據的首地址 uint8_t * src= rgb_frame->data[0]; // 實際內存一行數量 int srcStride = rgb_frame->linesize[0]; //int i=0; for (int i = 0; i < avCodecContext->height; ++i) { // memcpy(void *dest, const void *src, size_t n) //將rgb_frame中每一行的數據複製給nativewindow memcpy(dst + i * destStride, src + i * srcStride, srcStride); } //解鎖 ANativeWindow_unlockAndPost(nativeWindow); usleep(1000 * 16); }
在上面的代碼中,由於轉換成rgb格式事後的內容是存在ffmpeg所指向的地址而不是ANativeWindow所指向的所在地址,因此要繪製的話咱們須要將內容複製到ANativeWindow中。
5.完成事後得釋放資源,否則就形成內存泄露了。
ANativeWindow_release(nativeWindow); av_frame_free(&frame); av_frame_free(&rgb_frame); avcodec_close(avCodecContext); avformat_free_context(avFormatContext); env->ReleaseStringUTFChars(inputStr_, inputPath);
6.java層代碼,由於是原生繪製,因此須要傳入Surface,因此建立一個類繼承SurfaceView。
class VideoView : SurfaceView { constructor(context: Context) : super(context) {} constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init() } private fun init() { val holder = holder holder.setFormat(PixelFormat.RGBA_8888) } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} fun player(input: String) { Thread(Runnable { // 繪製功能 不須要交給SurfaveView VideoView.this.getHolder().getSurface() render(input, this@VideoView.holder.surface) }).start() } external fun render(input: String, surface: Surface) companion object { init { System.loadLibrary("avcodec-56") System.loadLibrary("avdevice-56") System.loadLibrary("avfilter-5") System.loadLibrary("avformat-56") System.loadLibrary("avutil-54") System.loadLibrary("postproc-53") System.loadLibrary("swresample-1") System.loadLibrary("swscale-3") System.loadLibrary("native-lib") } } }
以上就是對視頻的解封裝,解碼,轉換,繪製的一個過程,過程清晰明瞭,按着這個步奏來就應該來講比較簡單,另外,請在真機上測試,同時導入本身想測試的視頻,什麼格式均可以。