Android使用FFmpeg(三)--ffmpeg實現視頻播放

前言

若是你已經準備好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")
        }
    }
}

小結

以上就是對視頻的解封裝,解碼,轉換,繪製的一個過程,過程清晰明瞭,按着這個步奏來就應該來講比較簡單,另外,請在真機上測試,同時導入本身想測試的視頻,什麼格式均可以。

相關文章
相關標籤/搜索