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> 头文件当中,不要忘记包含该头文件。

 

posted @ 2018-10-18 14:41  夜行过客  阅读(5123)  评论(0编辑  收藏  举报