三寿羞、IOS-FFmpeg解碼

為了簡單化温艇,這里只是從H264視頻編碼格式,解碼為yuv420p的原始圖片格式。
代碼倉庫:https://github.com/wulang150/FFmpegTest.git
代碼文件:DecoderViewController.m

H264裸流格式:

這里談?wù)凥264裸流格式旭从,雖然對(duì)本次解碼表面上幫助不大,但理解了渔欢,你就更好地理解問題猛拴,解決問題。我之前做的項(xiàng)目是設(shè)備采集視頻數(shù)據(jù)霞捡,然后p2p發(fā)送給手機(jī)端坐漏,手機(jī)端進(jìn)行解碼,播放。所以我對(duì)H264裸流還是有一定的了解赊琳。
H264可以說分兩種格式:Annex-B和AVCC兩種格式街夭。
Annex-B格式:一般用于流在網(wǎng)絡(luò)中傳播,所以那些直播躏筏,監(jiān)控板丽,全部都是這個(gè)格式的H264。
AVCC格式:封裝在Mp4后的流的格式趁尼。

本次使用的是Annex-B格式的H264流埃碱,具體格式可以看看它的16進(jìn)制:

屏幕快照 2019-04-22 上午10.04.21.png

每一個(gè)包含完整信息的item(一般叫nal)都是用0x000001或0x00000001作為分割。
0x0000000167:sps
0x0000000168:pps
0x0000000165:IDR酥泞,主幀
其他都是P或B幀砚殿。sps跟pps包含了解碼主幀的一些信息,有了他們才可以正確解碼主幀芝囤。
具體可以參考:https://blog.csdn.net/romantic_energy/article/details/50508332

總的代碼

#import "DecoderViewController.h"
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>

#define INBUF_SIZE 4096

static AVFrame *pFrameYUV;
static struct SwsContext *img_convert_ctx;

@interface DecoderViewController ()

@end

@implementation DecoderViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"解碼";
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self mainFunc];
}

static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
{
    AVFrame *picture;
    int ret;
    picture = av_frame_alloc();
    if (!picture)
        return NULL;
    picture->format = pix_fmt;
    picture->width  = width;
    picture->height = height;
    /* allocate the buffers for the frame data */
    ret = av_frame_get_buffer(picture, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate frame data.\n");
        return NULL;
    }
    return picture;
}

static void pgm_save(AVFrame *frame, FILE *f)
{
    //進(jìn)行轉(zhuǎn)碼操作瓮具,轉(zhuǎn)成yuv420
    if(frame->format!=AV_PIX_FMT_YUV420P){
        if(!pFrameYUV){
            pFrameYUV = alloc_picture(AV_PIX_FMT_YUV420P, frame->width, frame->height);
        }
        if(!img_convert_ctx){
            //轉(zhuǎn)碼器
            img_convert_ctx = sws_getContext(frame->width, frame->height,
                                             frame->format,
                                             pFrameYUV->width, pFrameYUV->height,
                                             AV_PIX_FMT_YUV420P,
                                             SWS_BICUBIC, NULL, NULL, NULL);
        }
        sws_scale(img_convert_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, frame->height,
                                    pFrameYUV->data, pFrameYUV->linesize);
        frame = pFrameYUV;
    }
    printf("fmx=%d size=%dx%d\n",frame->format,frame->width,frame->height);
    int i;
    //Y
    int width = MIN(frame->linesize[0], frame->width);
    for(i=0;i<frame->height;i++)
    {
        fwrite(frame->data[0]+i*frame->linesize[0], 1, width, f);
    }
    //u
    width = MIN(frame->linesize[1], frame->width/2);
    for(i=0;i<frame->height/2;i++)
    {
        fwrite(frame->data[1]+i*frame->linesize[1], 1, width, f);
    }
    //v
    width = MIN(frame->linesize[2], frame->width/2);
    for(i=0;i<frame->height/2;i++)
    {
        fwrite(frame->data[2]+i*frame->linesize[2], 1, width, f);
    }
}

static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *f)
{
//    char buf[1024];
    int ret;
    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }
    while (ret >= 0) {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        fflush(stdout);
        printf("saving frame %3d ",dec_ctx->frame_number);
        pgm_save(frame, f);
    }
}

- (int)mainFunc{
    av_register_all();
    avcodec_register_all();
    
    const char *filename, *outfilename;
    const AVCodec *codec;
    AVCodecParserContext *parser;
    AVCodecContext *c= NULL;
    FILE *f, *outF;
    AVFrame *frame;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data;
    size_t   data_size;
    int ret;
    AVPacket *pkt;
    
    //input
    NSString *filePath = [CommonFunc getDocumentWithFile:@"movieH264.ts"];
    filename = [filePath cStringUsingEncoding:NSASCIIStringEncoding];
    //output
    filePath = [CommonFunc getDefaultPath:@"movie.yuv"];
    outfilename = [filePath cStringUsingEncoding:NSASCIIStringEncoding];
    
    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);
    /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
    memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    /* find video decoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "parser not found\n");
        exit(1);
    }
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }
//    c->pix_fmt = AV_PIX_FMT_YUV420P;
    /* For some codecs, such as msmpeg4 and mpeg4, width and height
     MUST be initialized there because this information is not
     available in the bitstream. */
    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
    f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
    outF = fopen(outfilename, "wb");
    if (!outF) {
        fprintf(stderr, "Could not open %s\n", outfilename);
        exit(1);
    }
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    while (!feof(f)) {
        /* read raw data from the input file */
        data_size = fread(inbuf, 1, INBUF_SIZE, f);
        if (!data_size)
            break;
        /* use the parser to split the data into frames */
        data = inbuf;
        while (data_size > 0) {
            //相當(dāng)于在annex-b格式的流中拆出每一個(gè)nal,可能得多次操作才有一個(gè)完整的pkt出來
            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                                   data, (int)data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
            if (ret < 0) {
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }
            data      += ret;
            data_size -= ret;
            if (pkt->size)
                decode(c, frame, pkt, outF);
        }
    }
    /* flush the decoder */
    decode(c, frame, NULL, outF);
    fclose(f);
    fclose(outF);
    sws_freeContext(img_convert_ctx);
    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_frame_free(&pFrameYUV);
    av_packet_free(&pkt);
    return 0;
}
@end

分析一下:
這里是解碼Annex-B格式的H264凡人,所以需要配置的參數(shù)少點(diǎn)名党,因?yàn)樾枰慕獯a信息都是在上面提到的sps,pps帶過來了挠轴,后面會(huì)說到解碼AVCC格式的H264传睹,需要配置的參數(shù)就會(huì)多點(diǎn):

//初始化解碼器
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
//通過解碼器,初始化解碼上下文
c = avcodec_alloc_context3(codec);
//打開解碼器
avcodec_open2(c, codec, NULL)
//傳入需要解碼的AVPacket
ret = avcodec_send_packet(dec_ctx, pkt);
//解碼得到AVFrame
ret = avcodec_receive_frame(dec_ctx, frame);

上面有個(gè)比較重要的步驟是:把H264流封裝成一個(gè)個(gè)AVPacket岸晦。AVPacket需要的信息就是前面提到的一個(gè)個(gè)nal欧啤,要怎么拆分,你可以使用AVCodecParserContext启上,當(dāng)然也可以自己拆分出來邢隧。代碼:

ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                                   data, (int)data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
使用AVCodecParserContext進(jìn)行拆分
把每一個(gè)nal賦值給packet->data。

解碼的api最新的改為了:
avcodec_send_packet
avcodec_receive_frame
之前的為:
avcodec_decode_video2
改為新的也更容易理解吧冈在,不一定是來一個(gè)nal倒慧,就可以解出來一個(gè),像有B幀的情況包券,來了B幀纫谅,還得繼續(xù)send入后面的幀才有可能解出來這個(gè)B幀,所以改為send跟receive比較好操控跟理解溅固。
avcodec_receive_frame上層加了個(gè)循環(huán)付秕,其實(shí)也是為了處理上面說的那種情況,B跟后面的P幀都來了侍郭,解碼了B幀后询吴,后面的P幀也是解碼的了掠河,所以這種情況avcodec_receive_frame可以有多個(gè)解碼的AVFrame,通過循環(huán)讀取完整猛计。
最后還得執(zhí)行:
decode(c, frame, NULL, outF);
把最后剩下解碼的幾幀個(gè)讀取出來唠摹。

解碼后:

進(jìn)行了一次yuv420p的轉(zhuǎn)換,為了統(tǒng)一為yuv420p格式有滑。因?yàn)樵紙D片格式也是有很多種的。統(tǒng)一轉(zhuǎn)為yuv420p格式后嵌削,我就可以統(tǒng)一按yuv420p的格式寫入文件了毛好。

得到最終的yuv文件后,你會(huì)發(fā)現(xiàn)原來的1多兆的H264流竟然解碼后達(dá)到了800多兆苛秕。所以壓縮率還是很高的肌访。

驗(yàn)證:

得到y(tǒng)uv文件后,怎么驗(yàn)證是解碼成功呢艇劫?這時(shí)候就得利用ffplay了(前提是的在mac安裝ffmpeg跟ffplay吼驶,前面有提到怎么安裝)〉晟罚可以播放蟹演,就證明解碼成功了。
指令為:

ffplay -i 11_42_18_movie.yuv -pixel_format yuv420p -video_size 1600x1200
-i:對(duì)應(yīng)的yuv文件
-pixel_format:對(duì)應(yīng)的格式
-video_size:1600x1200顷蟀,視頻的分辨率

H264裸流文件:
鏈接:https://pan.baidu.com/s/1fx60ynWJ2vVAGFfYZVatng 密碼:keyx
視頻是黑白的酒请,不是出問題了!鸣个!
除了使用我提供的裸流羞反,如果你有MP4視頻文件,也可以自己生成:

ffmpeg -i output.mp4 -an -vcodec copy -bsf:v h264_mp4toannexb output.h264
可以截取mp4得到更短的視頻:(截取前5秒)
ffmpeg -t 00:00:05 -i input.mp4 -vcodec copy -acodec copy output.mp4

但是囤萤,這個(gè)生成的H264裸流昼窗,有很多干擾的東西,所以用AVCodecParserContext是拆分不準(zhǔn)確的涛舍〕尉可以自己進(jìn)行拆分。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末富雅,一起剝皮案震驚了整個(gè)濱河市缤削,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吹榴,老刑警劉巖亭敢,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異图筹,居然都是意外死亡帅刀,警方通過查閱死者的電腦和手機(jī)让腹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扣溺,“玉大人骇窍,你說我怎么就攤上這事∽队啵” “怎么了腹纳?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驱犹。 經(jīng)常有香客問我嘲恍,道長,這世上最難降的妖魔是什么雄驹? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任佃牛,我火速辦了婚禮,結(jié)果婚禮上医舆,老公的妹妹穿的比我還像新娘俘侠。我一直安慰自己,他們只是感情好蔬将,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布爷速。 她就那樣靜靜地躺著,像睡著了一般霞怀。 火紅的嫁衣襯著肌膚如雪遍希。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天里烦,我揣著相機(jī)與錄音凿蒜,去河邊找鬼。 笑死胁黑,一個(gè)胖子當(dāng)著我的面吹牛废封,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丧蘸,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼漂洋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了力喷?” 一聲冷哼從身側(cè)響起刽漂,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弟孟,沒想到半個(gè)月后贝咙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拂募,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年庭猩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窟她。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔼水,死狀恐怖震糖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趴腋,我是刑警寧澤吊说,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站优炬,受9級(jí)特大地震影響颁井,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜穿剖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一蚤蔓、第九天 我趴在偏房一處隱蔽的房頂上張望卦溢。 院中可真熱鬧糊余,春花似錦、人聲如沸单寂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宣决。三九已至蘸劈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尊沸,已是汗流浹背威沫。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洼专,地道東北人棒掠。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像屁商,于是被迫代替她去往敵國和親烟很。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348