1.解码流程图

[Cmake-Android音视频]ffmpeg3.4软硬解码和多线程解码-LMLPHP

 

2.函数介绍

avcodec_register_all()

注册解码器格式,比如h264,mjpeg。

 

avcodec_find_decoder(...)

通过解码器ID查找相应的解码器。如果没有找到,应该是在编译ffmpeg的时候没有打开相应的解码器。此时,需要在ffmpeg的configure文件中打开,并重新编译出库文件。

avcodec_find_decoder_by_name(...)

通过名字来查找解码器。

//硬解码
avcodec_find_decoder_by_name("h264_mediacodec");

 

avcodec_open2(...)

打开解码器

//可以通过options来设置多线程解码
//所有可设置的参数在/libavcodec/options_table.h
//int thread_count   解码线程数
//time_base    时间基数
int avcodec_open2(AVCodecContext *avctx, const AVCodec*codec, AVDictionary **options)

 

avcodec_send_packet(...)

发送到线程中解码,packet会被复制一份,我们可以直接清理packet。

该函数会有缓存,发送到文件结尾后,可以通过发送NULL,将缓存数据取出。

 

avcodec_receive_frame(...)

从解码成功的数据中取出一个 frame, 解码的时候前几帧的可能获取失败,因为avcodec_send_packet(...)会有缓存, 导致播放的时候视频最后的几帧没有播放。

解决方案: 读到文件结尾处的时候,avcodec_send_packet()中的avpkt传NULL,然后在avcodec_receive_frame()一直读,,直到取不出帧为止。

接收已经解码好的数据。接收的数据和发送的数据并不一定是一一对应的。

 

3.关键结构体介绍

AVCodecContext

描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息。

//空间申请
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)

//释放
void avcodec_free_context(AVCodecContext **avctx);

//把avstream中的参数复制到codec中
avcodec_parameters_to_context(codec, p);

//关键参数
enum AVMediaType codec_type:编解码器的类型(视频,音频...)

struct AVCodec  *codec:采用的解码器AVCodec(H.264,MPEG2...)

int bit_rate:平均比特率

uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)

int width, height:如果是视频的话,代表宽和高

int sample_rate:采样率(音频)

int channels:声道数(音频)

enum AVSampleFormat sample_fmt:采样格式

 

AVFrame

用于存放解码后的数据,对视频来说是YUV,RGB,对音频来说是PCM

//分配内存空间
AVFrame *frame = av_frame_alloc()

//释放内存空间
void av_frame_free(AVFrame **frame)

//关键参数
//解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
uint8_t *data[AV_NUM_DATA_POINTERS];

//视频一行的数据大小 音频一个通道数据大小 存放的目的主要是为了按字节对齐
int linesize[AV_NUM_DATA_POINTERS];

//视频宽高
int width, height;

//音频单通道样本数 待定
int nb_samples;

//收到的当前帧的pts
int64_t pts;

//packet中的dts
int64_t pkt_dts;

//音频采样率  通道类型   通道数
int sample_rate;uint64_t channel_layout;int channels;

//视频---图片格式  音频---采样格式
int format; //AVPixelFormat AVSampleFormat

 

4.关键代码

多线程neon软解码关键代码  neon是在编译fffmpeg的时候配置的

//初始化解码器
avcodec_register_all();

//软解码器
AVCodec *codec = avcodec_find_decoder(vStream->codecpar->codec_id);

if (!codec)
{
    LOGI("avcodec_find_decoder failed");
    return env->NewStringUTF(hello.c_str());
}

//初始化解码器上下文
AVCodecContext *cc = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(cc, vStream->codecpar);
cc->thread_count = 8; //解码线程数

//打开解码器
 re = avcodec_open2(cc, 0, 0);
 if (re != 0)
 {
     LOGI("avcodec_open2 failed! %s", av_err2str(re));
     return env->NewStringUTF(hello.c_str());
 }

LOGI("avcodec_open2 success");

//读取帧数据
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
for (; ; )
{
    int re = av_read_frame(ic, pkt);
    if (re != 0)
    {
        LOGI("读到结尾处了");
        //传递NULL  保证缓存数据也能被取出
        avcodec_send_packet(cc, NULL);
        //int pos = 20 * r2d(vStream->time_base);
        //av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);
        continue;
    }

    //只测试视频
    if (pkt->stream_index != videoStream)
    {
        continue;
    }

    //发送到线程中解码
    re = avcodec_send_packet(cc, pkt);

    //清理
    av_packet_unref(pkt);

    if (re != 0)
    {
        LOGI("avcodec_send_packet FAILED");
        continue;
    }

    //保证能接受到所有的数据
    for (;;)
    {
        re = avcodec_receive_frame(cc, frame);
        if (re != 0)
        {
//                LOGI("avcodec_receive_frame FAILED");
            break;
        }

        LOGI("avcodec_receive_frame %lld, nb_samples = %d", frame->pts, frame->nb_samples);
    }


}

 

调用MedieCodec硬解码关键代码

//添加头文件
#include <libavcodec/jni.h>

//java加载的时候自动调用
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *res)
{
    //java虚拟机环境传递给ffmpeg
    av_jni_set_java_vm(vm, 0);
    return JNI_VERSION_1_4;
}

//硬解码
AVCodec *codec = avcodec_find_decoder_by_name("h264_mediacodec");

 

 

06-13 18:50