音视频基础知识及ffmpeg3.1.3解码视频文件到YUV数据
音视频基础知识及ffmpeg3.1.3解码视频文件到YUV数据
常见视频格式
- AVI,RMVB,MP4,FLV,MKV等等
这里的格式代表的是封装格式
就是把视频数据和音频数据打包成一个文件的规范
如何查看媒体信息
使用工具(MediaInfo)
可以查看到的视频信息如下
General
Complete name : C:\Users\Administrator\Desktop\72a739b.mp4 :
Format : MPEG-4 : //封装格式例如(mkv + mka + mks -> Matroska)
Format profile : Base Media / Version 2 : //格式简介
Codec ID : mp42
File size : 1.82 MiB
Duration : 19s 100ms
Overall bit rate mode : Variable
Overall bit rate : 799 Kbps
Encoded date : UTC 2017-06-27 05:37:46
Tagged date : UTC 2017-06-27 05:37:47
Video
ID : 2
Format : AVC : //H.264被MPEG组织称作AVC(Advanced Video Codec/先进视频编码)
Format/Info : Advanced Video Codec
Format profile : Main@L3
Format settings, CABAC : Yes
Format settings, ReFrames : 2 frames : //参考帧数(B帧和P帧中的预测器里所使用的之前出现的帧的数量,范围0-16,一般设2―7之间)
Codec ID : avc1
Codec ID/Info : Advanced Video Coding
Duration : 19s 100ms
Bit rate : 707 Kbps
Width : 568 pixels
Height : 320 pixels
Display aspect ratio : 16:9
Rotation : 90°
Frame rate mode : Constant : //帧率可变模式(Constant) 帧率可变模式(Variable)
Frame rate : 30.000 fps
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Bits/(Pixel*Frame) : 0.130
Stream size : 1.61 MiB (89%)
Title : Core Media Video
Encoded date : UTC 2017-06-27 05:37:46
Tagged date : UTC 2017-06-27 05:37:47
Color range : Limited
Color primaries : BT.709
Transfer characteristics : BT.709
Matrix coefficients : BT.709
Audio
ID : 1
Format : AAC
Format/Info : Advanced Audio Codec
Format profile : LC
Codec ID : 40
Duration : 19s 98ms
Source duration : 19s 156ms
Bit rate mode : Variable
Bit rate : 85.7 Kbps
Channel(s) : 2 channels
Channel(s)_Original : 1 channel
Channel positions : Front: C
Sampling rate : 44.1 KHz
Compression mode : Lossy ://有损压缩
Stream size : 200 KiB (11%)
Source stream size : 200 KiB (11%)
Title : Core Media Audio
Encoded date : UTC 2017-06-27 05:37:46
Tagged date : UTC 2017-06-27 05:37:47
AVC说明
AVC的规格分为三等,从低到高分别为:Baseline、Main、High
- Baseline(最低Profile)级别支持I/P 帧,只支持无交错(Progressive)和CAVLC,一般用于低阶或需要额外容错的应用,比如视频通话、手机视频等;
- Main(主要Profile)级别提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),同样提供对于CAVLC 和CABAC 的支持,用于主流消费类电子产品规格如低解码(相对而言)的mp4、便携的视频播放器、PSP和Ipod等;
- High(高端Profile,也叫FRExt)级别在Main的基础上增加了8x8 内部预测、自定义量化、无损视频编码和更多的YUV 格式(如4:4:4)用于广播及视频碟片存储(蓝光影片),高清电视的应用。
AVC 的规格主要是针对兼容性的,不同的规格能在相同级别上的平台应用。
至于Baseline@L x.x、Main@L x.x、High@L x.x形式则是在不同级别下的码流级别,数值越大码流就越大,更耗费资源。所以就码流而言High@L3.0<High@L4.0<High@L5.1。
Codec ID:
FOURCC:AVC1 描述:H.264 bitstream without start codes.
FOURCC:H264 描述:H.264 bitstream with start codes.
带有开始码的H.264视频一般是用于无线发射、有线广播或者HD-DVD中的。这些数据流的开始都有一个开始码:0x000001 或者 0x00000001.
有开始码的H.264视频主要是存储在MP4格式的文件中的。它的数据流的开始是1、2或者4个字节表示长度数据
视频播放原理
视音频技术主要包含:封装技术,视频压缩编码技术以及音频压缩编码技术
如何播放视频
- 播放视频要经过以下几个步骤:解协议(网络传输协议,或者本地文件协议,这里把本地文件也看做一种协议),解封装,解码视频音频,音视频同步,音视频渲染
视频数据格式(RGB,YUV)
H264
H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。结构如下图:
vMb77Zj.jpgNAL全称Network Abstract Layer, 即网络抽象层
在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……
其中每个NALU之间通过startcode(起始码)进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。
H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段。
NAL nal_unit_type 为序列参数集(SPS)、图像参数集(PPS)、增强信息(SEI)不属于帧的概念。表示后面的数据信息为序列参数集(SPS)、图像参数集(PPS)、增强信息(SEI)
NAL类型有:
NAL_SLICE = 1 非关键帧
NAL_SLICE_DPA = 2
NAL_SLICE_DPB = 3
NAL_SLICE_DPC =4
NAL_SLICE_IDR =5 关键帧
NAL_SEI = 6
NAL_SPS = 7 SPS帧
NAL_PPS = 8 PPS帧
NAL_AUD = 9
NAL_FILLER = 12
ffmpeg解码视频文件简单使用
程序执行流程图
1WD8LKq.jpg开发环境是Android studio2.3.3
以下是我使用ffmpeg3.1.3版本,解码视频文件到YUV数据的代码
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "PLAYER-JNI"
#define ALOG(priority, tag, fmt...) \
__android_log_print(ANDROID_##priority, tag, fmt)
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, TAG, __VA_ARGS__))
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
void *startDecodeVideo(void *ptr) {
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVFrame *pFrame, *pFrameYUV;
AVPacket *packet;
unsigned char *out_buffer_video;
FILE *fp_yuv;
int ret, got_picture;
struct SwsContext *img_convert_ctx;
int i, videoindex = -1;
char *mFile = (char *) ptr;
ALOGD("startDecodeVideo mFile=%s", mFile);
AVFormatContext *pFormatCtx = avformat_alloc_context();
av_register_all();
if (avformat_open_input(&pFormatCtx, mFile, NULL, NULL) != 0) {
ALOGD("Couldn't open input stream.\n");
pthread_exit(NULL);
}
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
ALOGD("Couldn't find stream information.\n");
return 0;
}
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
if (videoindex == -1) {
ALOGD("Didn't find a video stream.\n");
return NULL;
}
packet = (AVPacket *) av_malloc(sizeof(AVPacket));
pCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar);
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
ALOGD("Codec not found.\n");
return NULL;
}
pCodecCtx->codec_id = pCodec->id;
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
ALOGD("Could not open codec.\n");
return NULL;
}
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
out_buffer_video = (unsigned char *) av_malloc(
(size_t) av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height, 1));
/**
* 也可以使用avpicture_fill方法替换av_image_get_buffer_size,avpicture_fill为它的简单封装
*
*/
/**
int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
enum AVPixelFormat pix_fmt, int width, int height)
{
return av_image_fill_arrays(picture->data, picture->linesize,
ptr, pix_fmt, width, height, 1);
}
*/
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer_video,
AV_PIX_FMT_YUV420P, pCodecCtx->width,
pCodecCtx->height, 1);
/**
* 可以使用avpicture_get_size替换av_image_fill_arrays,avpicture_get_size是它的简单封装
*/
/**
int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height)
{
return av_image_get_buffer_size(pix_fmt, width, height, 1);
}
*/
fp_yuv = fopen("/storage/emulated/0/output.yuv", "wb+");
ALOGD("Decode width=%d height=%d pix_fmt=%d", pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt);
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
i = 0;
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == videoindex) {
//Decode
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0) {
ALOGD("Decode Error.\n");
return NULL;
}
if (got_picture) {
sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize,
0,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
ALOGD("Decode 第%d帧", i++);
int y_size = pCodecCtx->width * pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V
}
}
av_packet_unref(packet);
}
sws_freeContext(img_convert_ctx);
fclose(fp_yuv);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
ALOGD("Decode end");
return NULL;
}
JNIEXPORT void JNICALL
Java_ican_ytx_com_ffmpegdecodesample_MainActivity_startDecodeVideo(
JNIEnv *env,
jobject /* this */,
jobject assetMgr, jstring filename) {
const char *utf8 = env->GetStringUTFChars(filename, NULL);
char *mFile = (char *) calloc(strlen(utf8) + 1, sizeof(char *));
memcpy(mFile, utf8, strlen(utf8));
pthread_t mPlayer;
pthread_create(&mPlayer, NULL, startDecodeVideo, mFile);
}
#ifdef __cplusplus
}
#endif
代码执行完成后会在/storage/emulated/0/
目录下生成output.yuv
文件,你可以使用yuv播放器来播放该文件。生成路径可自行修改,以下是github源码下载地址: