音视频基础知识及ffmpeg3.1.3解码视频文件到YUV数据

音视频基础知识及ffmpeg3.1.3解码视频文件到YUV数据

常见视频格式

  1. 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

  1. Baseline(最低Profile)级别支持I/P 帧,只支持无交错(Progressive)和CAVLC,一般用于低阶或需要额外容错的应用,比如视频通话、手机视频等;
  2. Main(主要Profile)级别提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),同样提供对于CAVLC 和CABAC 的支持,用于主流消费类电子产品规格如低解码(相对而言)的mp4、便携的视频播放器、PSP和Ipod等;
  3. 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个字节表示长度数据

视频播放原理

视音频技术主要包含:封装技术,视频压缩编码技术以及音频压缩编码技术

如何播放视频

  1. 播放视频要经过以下几个步骤:解协议(网络传输协议,或者本地文件协议,这里把本地文件也看做一种协议),解封装,解码视频音频,音视频同步,音视频渲染
P7cqaVZ.jpg

视频数据格式(RGB,YUV)

H264

H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。结构如下图:

vMb77Zj.jpg

NAL全称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源码下载地址:

https://github.com/ytxhao/FFmpegDecodeSample.git

参考资料:

1.FFMPEG视音频编解码零基础学习方法
2.AVC编码中的规格

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容