iOS FFmpeg零到自己的播放器1,解碼


前言:

網(wǎng)上有很多基于FFmpeg寫的媒體播放器,都寫得很好,但是由于FFmpeg庫本身接口多,加上作者們編寫代碼的時候習慣進行封裝,對初學者來說是不友好的,所以打算寫一個系列,從最基礎開始,編寫屬于自己的媒體播放器,記錄下自己學習的過程,也方便跟我一樣的初學者能更快對FFmpeg有所了解。


基礎概念:

容器/文件:即特定格式的多媒體文件,比如MP4、flv、mov等。
媒體流:表示時間軸上一段連續(xù)數(shù)據(jù),如一段聲音數(shù)據(jù)、一段視頻數(shù)據(jù)或一段字幕數(shù)據(jù),可以是壓縮的,也可以是非壓縮的,壓縮數(shù)據(jù)需要關聯(lián)特定的編解碼器。
數(shù)據(jù)幀/數(shù)據(jù)包:通常,一個媒體流是由大量的數(shù)據(jù)幀組成的,對于壓縮數(shù)據(jù),幀對應這編解碼器的最小處理單元,分屬于不同媒體流的數(shù)據(jù)幀交錯存儲于容器中。
編解碼器:編解碼器是以幀為單位實現(xiàn)壓縮數(shù)據(jù)和原始數(shù)據(jù)之間的相互轉(zhuǎn)換的。

基本結(jié)構(gòu)體介紹:

AVFormatContext:對容器或者說媒體文件層次的一個抽象,該文件中(或者說這個容器里),包含了多路流(音頻流、視頻流、字幕流等)。
AVStream:對流的抽象,在每一路流中都會描述這路流的編碼格式。
AVCodecContext:對編碼格式的抽象。
AVCodec:對編解碼器的抽象。
AVPacket:對壓縮數(shù)據(jù)的抽象。
AVFrame:對原始數(shù)據(jù)的抽象。

正文

目標:把一個視頻文件,解碼成單獨的音頻PCM文件和視頻YUV文件,然后使用命令行工具ffplay去驗證播放這兩個文件。

本文Demo:https://github.com/huisedediao/FFmpegDecoder
ffplay使用:http://www.itdecent.cn/p/8cce27b1e294

新建工程,將編譯好的FFmpeg集成到項目中,集成過程可以看這篇文章:http://www.itdecent.cn/p/d1ed7b860f1b

引入頭文件:

#import <libavformat/avformat.h>
#import <libavcodec/avcodec.h>
#import <libswscale/swscale.h>
#import <libswresample/swresample.h>
#import <libavutil/pixdesc.h>


設置好解碼出來的音頻數(shù)據(jù)和視頻數(shù)據(jù)的存儲路徑,運行代碼的時候記得改為自己桌面的路徑:

    NSString *audioStorePath = [NSString stringWithFormat:@"/Users/xxb/Desktop/audioData.pcm"];
    XBDataWriter *audioDataWriter = [XBDataWriter new];//用于寫音頻數(shù)據(jù)
    
    NSString *videoStorePath = [NSString stringWithFormat:@"/Users/xxb/Desktop/videoData.yuv"];
    XBDataWriter *videoDataWriter = [XBDataWriter new];//用于寫視頻數(shù)據(jù)
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:audioStorePath])
    {
        [[NSFileManager defaultManager] removeItemAtPath:audioStorePath error:nil];
    }


注冊FFmpeg服務:

  av_register_all();


打開文件,讀取文件信息

    NSString *mp4Path = [[NSBundle mainBundle] pathForResource:@"IMG_0427" ofType:@"mp4"];
    AVFormatContext *formatCtx = avformat_alloc_context();
    
    //打開文件
    int status = 0;
    status = avformat_open_input(&formatCtx, [mp4Path UTF8String], NULL, NULL);
    if (status < 0)
    {
        NSLog(@"打開文件失敗");
        return;
    }
    
    //讀取文件信息
    status = avformat_find_stream_info(formatCtx, NULL);
    if (status < 0)
    {
        NSLog(@"無法獲取流信息");
        return;
    }


記錄音視頻流序號:

    int videoStreamIndex;//記錄視頻流是第幾路
    int audioStreamIndex;//記錄音頻流是第幾路
    
    //尋找音視頻流
    for (int i = 0; i < formatCtx->nb_streams; i++)
    {
        AVStream *stream = formatCtx->streams[i];
        
        if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)//視頻
        {
            videoStreamIndex = i;
        }
        else if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)//音頻
        {
            audioStreamIndex = i;
        }
    }


打開視頻解碼器:

    //打開視頻解碼器
    AVStream *videoStream = formatCtx->streams[videoStreamIndex];
    AVCodecContext *videoCodecCtx = videoStream->codec;
    //尋找解碼器
    AVCodec *videoCodec = avcodec_find_decoder(videoCodecCtx->codec_id);
    if (videoCodec == nil)
    {
        NSLog(@"尋找視頻解碼器失敗");
        return;
    }
    //打開解碼器
    int openVideoCodecErr = avcodec_open2(videoCodecCtx, videoCodec, NULL);
    if (openVideoCodecErr < 0)
    {
        NSLog(@"打開視頻解碼器失敗");
        return;
    }


打開音頻解碼器:

    //打開音頻解碼器
    AVStream *audioStream = formatCtx->streams[audioStreamIndex];
    AVCodecContext *audioCodecCtx = audioStream->codec;
    AVCodec *audioCodec = avcodec_find_decoder(audioCodecCtx->codec_id);
    if (audioCodec == nil)
    {
        NSLog(@"尋找音頻解碼器失敗");
        return;
    }
    int openAudioCodecErr = avcodec_open2(audioCodecCtx, audioCodec, NULL);
    if (openVideoCodecErr < 0)
    {
        NSLog(@"打開音頻解碼器失敗");
        return;
    }


初始化存儲解碼后視頻數(shù)據(jù)的對象、構(gòu)建視頻的格式轉(zhuǎn)換對象

    //初始化視頻數(shù)據(jù)存儲對象
    AVFrame *videoFrame = av_frame_alloc();
    //構(gòu)建視頻格式轉(zhuǎn)換對象
    AVPicture picture;
    int createPic = avpicture_alloc(&picture,
                                        AV_PIX_FMT_YUV420P,
                                        videoCodecCtx->width,
                                        videoCodecCtx->height);
    
    struct SwsContext *swsCtx = NULL;
    if (createPic != 0)
    {
        NSLog(@"創(chuàng)建pic失敗");
        return;
    }
    swsCtx = sws_getCachedContext(swsCtx,
                                  videoCodecCtx->width,
                                  videoCodecCtx->height,
                                  videoCodecCtx->pix_fmt,
                                  videoCodecCtx->width,
                                  videoCodecCtx->height,
                                  AV_PIX_FMT_YUV420P,
                                  SWS_FAST_BILINEAR,
                                  NULL,
                                  NULL,
                                  NULL);


初始化存儲解碼后音頻數(shù)據(jù)的對象、構(gòu)建音頻格式轉(zhuǎn)換對象

    //初始化音頻數(shù)據(jù)存儲對象
    AVFrame *audioFrame = av_frame_alloc();
    //構(gòu)建音頻格式轉(zhuǎn)換對象
    SwrContext *swrCtx = NULL;
    if (audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16)
    {
        NSLog(@"需要轉(zhuǎn)換音頻格式");
        int outputChannel = audioCodecCtx->channels;
        int outputSampleRate = audioCodecCtx->sample_rate;
        NSLog(@"channels:%d,sampleRate:%d,bitRate:%zd",outputChannel,outputSampleRate,audioCodecCtx->bit_rate);
        int64_t in_ch_layout = audioCodecCtx->channel_layout;
        enum AVSampleFormat in_sample_fmt = audioCodecCtx->sample_fmt;
        int in_sample_rate = audioCodecCtx->sample_rate;
        
        swrCtx = swr_alloc_set_opts(NULL,
                                    outputChannel,
                                    AV_SAMPLE_FMT_S16,
                                    outputSampleRate,
                                    in_ch_layout,
                                    in_sample_fmt,
                                    in_sample_rate,
                                    0,
                                    NULL);
        if (!swrCtx || swr_init(swrCtx))
        {
            if (swrCtx)
            {
                swr_free(&swrCtx);
            }
        }
    }


解碼,并且處理解碼后的數(shù)據(jù):


    AVPacket packet;
    int gotFrame = 0;
    NSLog(@"decode start");
    
    int swrBufferSize = 1;
    void *swrBuffer = malloc(sizeof(char) * swrBufferSize);
    
    while (true)
    {
        if (av_read_frame(formatCtx, &packet) < 0)
        {
            NSLog(@"end of file or error");
            break;
        }
        
        int packetStreamIndex = packet.stream_index;
        if (packetStreamIndex == videoStreamIndex)//視頻
        {
            int len = avcodec_decode_video2(videoCodecCtx, videoFrame, &gotFrame, &packet);
            if (len < 0)
            {
                break;
            }
            if (gotFrame)
            {
                //處理解碼后的視頻數(shù)據(jù)
                @autoreleasepool {//不加內(nèi)存直接炸了
                    NSData *luma;
                    NSData *chromaB;
                    NSData *chromaR;
                    if (videoCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P || videoCodecCtx->pix_fmt == AV_PIX_FMT_YUVJ420P)
                    {
                        luma = copyFrameData(videoFrame->data[0],
                                             videoFrame->linesize[0],
                                             videoCodecCtx->width,
                                             videoCodecCtx->height);
                        
                        chromaB = copyFrameData(videoFrame->data[1],
                                                videoFrame->linesize[1],
                                                videoCodecCtx->width / 2,
                                                videoCodecCtx->height / 2);
                        
                        chromaR = copyFrameData(videoFrame->data[2],
                                                videoFrame->linesize[2],
                                                videoCodecCtx->width / 2,
                                                videoCodecCtx->height / 2);
                    }
                    else
                    {
                        sws_scale(swsCtx,
                                  (const uint8_t **)videoFrame->data,
                                  videoFrame->linesize,
                                  0,
                                  videoCodecCtx->height,
                                  picture.data,
                                  picture.linesize);
                        luma = copyFrameData(picture.data[0],
                                             picture.linesize[0],
                                             videoCodecCtx->width,
                                             videoCodecCtx->height);
                        
                        chromaB = copyFrameData(picture.data[1],
                                                picture.linesize[1],
                                                videoCodecCtx->width / 2,
                                                videoCodecCtx->height / 2);
                        
                        chromaR = copyFrameData(picture.data[2],
                                                picture.linesize[2],
                                                videoCodecCtx->width / 2,
                                                videoCodecCtx->height / 2);
                    }
                    //寫數(shù)據(jù)到本地,這里建議用模擬器跑,因為出來的文件賊大
                    [videoDataWriter writeData:luma toPath:videoStorePath];
                    [videoDataWriter writeData:chromaB toPath:videoStorePath];
                    [videoDataWriter writeData:chromaR toPath:videoStorePath];
                }
            }
        }
        else if (packetStreamIndex == audioStreamIndex)//音頻
        {
            int len = avcodec_decode_audio4(audioCodecCtx, audioFrame, &gotFrame, &packet);
            if (len < 0)
            {
                break;
            }
            if (gotFrame)
            {
                //處理解碼后的音頻數(shù)據(jù)
                void *audioData;
                int numFrames;
                int channels = audioCodecCtx->channels;
                int samplesCount = (int)(audioFrame->nb_samples);
                if (swrCtx)
                {
                    int bufSize = av_samples_get_buffer_size(NULL,
                                                             channels,
                                                             samplesCount,
                                                             AV_SAMPLE_FMT_S16,
                                                             1);

                    if (!swrBuffer || swrBufferSize < bufSize)
                    {
                        swrBufferSize = bufSize;
                        swrBuffer = realloc(swrBuffer, swrBufferSize);
                    }
                    Byte *outbuf[2] = {swrBuffer,0};
                    numFrames = swr_convert(swrCtx,
                                            outbuf,
                                            samplesCount,
                                            (const uint8_t **)audioFrame->data,
                                            samplesCount);
                    audioData = swrBuffer;
                }
                else
                {
                    audioData = audioFrame->data[0];
                    numFrames = audioFrame->nb_samples;
                }
                //AV_SAMPLE_FMT_S16 16位
                //channels 為 1
                //單聲道16位,一幀占2個字節(jié)
                int audioDataLen = numFrames * channels * 16 / 8;
                //pcm數(shù)據(jù)寫到本地
                [audioDataWriter writeBytes:audioData len:audioDataLen toPath:audioStorePath];
            }
        }
    }


關閉資源占用:


    //關閉packet
    av_free_packet(&packet);
    
    //關閉音頻資源
    if (swrBuffer)
    {
        free(swrBuffer);
        swrBuffer = NULL;
        swrBufferSize = 0;
    }
    if (swrCtx)
    {
        swr_free(&swrCtx);
        swrCtx = NULL;
    }
    if (audioFrame)
    {
        av_free(audioFrame);
        audioFrame = NULL;
    }
    if (audioCodecCtx)
    {
        avcodec_close(audioCodecCtx);
        audioCodecCtx = NULL;
    }
    
    //關閉視頻資源
    if (swsCtx)
    {
        sws_freeContext(swsCtx);
        swsCtx = NULL;
    }
    if (createPic == 0)
    {
        avpicture_free(&picture);
        createPic = -1;
    }
    if (videoFrame)
    {
        av_free(videoFrame);
        videoFrame = NULL;
    }
    if (videoCodecCtx)
    {
        avcodec_close(videoCodecCtx);
        videoCodecCtx = NULL;
    }
    
    //關閉連接資源
    if (formatCtx)
    {
        avformat_close_input(&formatCtx);
        formatCtx = NULL;
    }
    
    NSLog(@"end");


拷貝視頻數(shù)據(jù)的方法:

static NSData * copyFrameData(UInt8 *src, int linesize, int width, int height)
{
    width = MIN(linesize, width);
    NSMutableData *md = [NSMutableData dataWithLength: width * height];
    Byte *dst = md.mutableBytes;
    for (NSUInteger i = 0; i < height; ++i) {
        memcpy(dst, src, width);
        dst += width;
        src += linesize;
    }
    return md;
}



至此,對一個MP4的解碼就結(jié)束了,代碼運行后產(chǎn)生的 audioData.pcm 和 videoData.yuv ,可以用ffplay去驗證:

//播放視頻
ffplay -s 1280*720 /Users/xxb/Desktop/videoData.yuv

//播放音頻
ffplay -f s16le -ar 44100 -ac 1 /Users/xxb/Desktop/audioData.pcm 


覺得有收獲的小伙伴,動動小手給個贊哦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容