FFmpeg(9)-解碼器解碼代碼演示(FFmpeg調用MediaCodec實現硬解碼、多線程解碼、及音視頻解碼性能測試)


一.AVFrame

用來存放解碼后的數據。

【相關函數】

AVFrame *frame = av_frame_alloc();                       // 空間分配,分配一個空間並初始化。

void av_frame_free(AVFrame **frame);                   // 空間釋放。兩種釋放方式,一種是將引用計數-1,

int av_frame_ref(AVFrame *dst, const AVFrame *src); // 引用計數增加1。比如要在多線程訪問的時候復制到另外一邊,就可以利用引用計數的變化。

AVFrame *av_frame_clone(const AVFrame *src);      // 復制。也是重新創建一個空間,然后引用計數加+1。與AVPacket的區別在於:AVFrame的復制開銷更大。1920*1080p的視頻,一幀可能就有幾MB,一秒鍾可能就有幾百MB,所以做一幀畫面的內存復制可能都耗費到毫秒級別,不像AVPacket可能只有微秒級別,會影響幀率。所以在它的空間復制上一定要慎重,所以我們一般用引用計數的方式來做。

void av_frame_unref(AVFrame *frame);                   // 直接把對象的引用計數-1.

 

【結構體包含的內容】

uint8_t *data[AV_NUM_DATA,POINTERS]                // 存放的數據。

int linesize[AV_NUM_DATA,POINTERS]                    // 對於視頻就是一行數據的大小。對於音頻就是一個通道數據的大小。

int width, int height, int nb_samples                         // 視頻部分, 音頻相關(單通道的樣本數量)

int64_ t pts                                                             // 實際這一幀的pts。

int64_t pkt_dts                                                       // 對應包當中的dts。

int sample_rate;                                                      // 樣本率

uint64_t channel_layout;                                         // 通道類型

int channels;                                                          // 通道數量

int format;                                                              // 像素格式。區分音頻和視頻。視頻的話就是AVPixelFormat,音頻的話就是AVSampleFormat

二.解碼器解碼代碼演示

// 初始化解封裝
    av_register_all();
    // 注冊解碼器
    avcodec_register_all();

    // 初始化網絡
    avformat_network_init();
    // 打開文件
    AVFormatContext *ic = NULL;
    char path[] = "sdcard/1080.mp4";
    // char path[] = "/sdcard/qingfeng.flv";
    int ret = avformat_open_input(&ic, path, 0, 0);
    if (ret != 0) {
        LOGE("avformat_open_input() called failed: %s", av_err2str(ret));
        return env->NewStringUTF(hello.c_str());
    }
    LOGI("avformat_open_input(): File open success.");
    LOGI("File duration is: %lld, nb_stream is: %d", ic->duration, ic->nb_streams);
    if (avformat_find_stream_info(ic, 0) >=0 ) {
        LOGI("File duration is: %lld, nb_stream is: %d", ic->duration, ic->nb_streams);
    }

    /**幀率*/
    int fps = 0;
    /*視頻流索引*/
    int videoStream = 0;
    /*音頻流索引*/
    int audioStream = 1;

    // 遍歷獲得音/視頻流索引
    for (int i = 0; i < ic->nb_streams; i++) {
        AVStream *as = ic->streams[i];
        if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            LOGI("視頻數據");
            videoStream = i;
            fps = (int)r2d(as->avg_frame_rate);
            LOGI("fps = %d, width = %d, height = %d, codecid = %d, format = %d",
                 fps,
                 as->codecpar->width,
                 as->codecpar->height,
                 as->codecpar->codec_id,
                 as->codecpar->format);
        } else if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            LOGI("音頻數據");
            audioStream = i;
            LOGI("sample_rate = %d, channels = %d, sample_format = %d",
                 as->codecpar->sample_rate,
                 as->codecpar->channels,
                 as->codecpar->format
            );
        }
    }

    // 也可以利用av_find_best_stream()函數來查找音視頻流索引
    // audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    // LOGI("av_find_best_stream, audio index is: %d", audioStream);


    // 查找視頻解碼器
    AVCodec *vCodec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id); // 軟解
    // vCodec = avcodec_find_decoder_by_name("h264_mediacodec"); // 硬解
    if (!vCodec) {
        LOGE("avcodec_find_decoder() failed. can not found video decoder.");
        return env->NewStringUTF(hello.c_str());
    }
    // 配置解碼器上下文
    AVCodecContext *vc = avcodec_alloc_context3(vCodec);
    // 將AVStream里面的參數復制到上下文當中
    avcodec_parameters_to_context(vc, ic->streams[videoStream]->codecpar);
    vc->thread_count = 8;
    // 打開解碼器
    ret = avcodec_open2(vc, vCodec, 0);
    if (ret != 0) {
        LOGE("avcodec_open2() failed. can not open video decoder, line is: %d", __LINE__);
        return env->NewStringUTF(hello.c_str());
    }

    // 查找音頻解碼器
    AVCodec *aCodec = avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id); // 軟解
    // aCodec= avcodec_find_decoder_by_name("h264_mediacodec"); // 硬解
    if (!aCodec) {
        LOGE("avcodec_find_decoder() failed. can not found audio decoder.");
        return env->NewStringUTF(hello.c_str());
    }
    // 配置解碼器上下文
    AVCodecContext *ac = avcodec_alloc_context3(aCodec);
    // 將AVStream里面的參數復制到上下文當中
    avcodec_parameters_to_context(ac, ic->streams[audioStream]->codecpar);
    ac->thread_count = 8;
    // 打開解碼器
    ret = avcodec_open2(ac, aCodec, 0);
    if (ret != 0) {
        LOGE("avcodec_open2() failed. can not open audio decoder");
        return env->NewStringUTF(hello.c_str());
    }

    // 讀取幀數據
    AVPacket *packet = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    int64_t start = getNowMs();
    int frameCount = 0;

    for (;;) {

        if (getNowMs() - start >= 3000) {
            LOGI("now decoder fps is: %d", frameCount / 3);
            start = getNowMs();
            frameCount = 0;
        }
        int ret = av_read_frame(ic, packet);
        if (ret != 0) {
            LOGE("讀取到結尾處");
            int pos = 20 * r2d(ic->streams[videoStream]->time_base);
            // 改變播放進度
            av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
            continue;
        }

//        LOGI("Read a Packet. streamIndex=%d, size=%d, pts=%lld, flag=%d",
//             packet->stream_index,
//             packet->size,
//             packet->pts,
//             packet->flags
//        );

        AVCodecContext *cc = vc;
        if (packet->stream_index == audioStream) cc = ac;
        // 發送到線程中去解碼(將packet寫入到解碼隊列當中去)
        ret = avcodec_send_packet(cc, packet);
        // 清理
        int p = packet->pts;
        av_packet_unref(packet);
        if (ret != 0) {
            // LOGE("avcodec_send_packet failed.");
            continue;
        }

        for(;;) {
            // 從已解碼成功的數據當中取出一個frame, 要注意send一次,receive不一定是一次
            ret = avcodec_receive_frame(cc, frame);
            if (ret != 0) {
                break;
            }
            if (cc == vc) {
                frameCount++;
            }
            // LOGI("Receive a frame.........");
        }
    }

    // 關閉上下文
    avformat_close_input(&ic);
    return env->NewStringUTF(hello.c_str());

另外:硬件解碼器需要進行注冊,即需要把Java虛擬機的環境傳遞給FFmpeg,因此還需要添加下列代碼,否則解碼器無法打開。

extern "C"
JNIEXPORT
jint JNI_OnLoad(JavaVM *vm,void *res)
{
    av_jni_set_java_vm(vm,0);
    return JNI_VERSION_1_4;
}

 av_jni_set_java_vm(vm,0); 包含在 <libavcodec/jni.h> 頭文件當中,不要忘記包含該頭文件。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM