[Cmake-Android]音视频总结:
-
[Cmake-Android音视频]SDK,NDK基本介绍
-
[Cmake-Android音视频]NDK-r14b编译ffmpeg3.4支持neon,硬解码
-
[Cmake-Android音视频]创建支持ffmpeg3.4的项目
-
[Cmake-Android音视频]ffmpeg3.4实现解封装
-
[Cmake-Android音视频]ffmpeg3.4软硬解码和多线程解码
-
[Cmake-Android音视频]ffmpeg3.4视频格式转换和显示
-
[Cmake-Android音视频]ffmpeg3.4音频重采样
-
[Cmake-Android音视频]OpenSLES音频播放
1.解码流程图
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");