美文网首页
FFMPEG-iOS-视频解码--简书

FFMPEG-iOS-视频解码--简书

作者: 幻梦_ | 来源:发表于2017-11-22 15:19 被阅读0次
    我们下载的视频文件如MP4,MKV、FLV等都属于封装格式,所以当我们播放一个媒体文件时,通常需要经过以下几个步骤 播放视频流程.png

    1. 注册组件:av_register_all()
    2. 打开封装格式->打开文件:av_open_input_file()
    3.查找视频流->拿到视频信息:av_find_stream_info()
    4.穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO
    5.查找对应视频的解码器:avcodec_find_decoder()
    6. 打开解码器:avcodec_open2()
    7. 为解码帧分配内存:avcodec_alloc_frame()
    8. 读取视频压缩数据->循环读取 取出每一帧数据:av_read_frame()
    9. 判断帧的类型,对于视频帧调用:avcodec_decode_video()
    10. 解码完后,释放解码器:avcodec_close()
    11. 关闭输入文件:av_close_input_file()

    流程图如下:

    image.png

    XCODE环境配置

    添加系统库.png
    关联ffmpeg头文件.png

    引入FFmpeg库头文件

    //核心库
    #include "libavcodec/avcodec.h"
    //封装格式处理库
    #include "libavformat/avformat.h"
    //工具库
    #include "libavutil/imgutils.h"
    //视频像素数据格式库
    #include "libswscale/swscale.h"
    //音频采样数据格式库
    #include "libswresample/swresample.h"
    

    视频解码实现

    +(void)ffmepgDecodeVideo:(NSString*)jinfilePath outfilePath:(NSString *)joutfilePath{
        
        //第一步:组册组件
        const char *infilePath = [jinfilePath UTF8String];
        const char *outfilePath = [joutfilePath UTF8String];
        av_register_all();
        
        AVFormatContext *avformat_context = avformat_alloc_context();
        //第二步:打开封装格式->打开文件
        //参数一:封装格式上下文
        //作用:保存整个视频信息(解码器、编码器等等...)
        //信息:码率、帧率等...
        int avformat_open_input_result = avformat_open_input(&avformat_context,
                                                             infilePath,
                                                             NULL,
                                                             NULL);
    
        if (avformat_open_input_result != 0){
            NSLog(@"打开文件失败");
            return;
        }
        //第三步:查找视频流->拿到视频信息
        //参数一:封装格式上下文
        //参数二:指定默认配置
        int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,
                                                                         NULL);
    
        if (avformat_find_stream_info_result < 0){
            NSLog(@"查找失败");
            return;
        }
    
        //第四步:查找视频解码器
        //1、查找视频流索引位置
        int av_stream_index = -1;
    
        for(int i=0;i<avformat_context->nb_streams;i++){
            //判断流类型:视频流、音频流、字母流等等...
            AVCodecContext *codec = avformat_context->streams[i]->codec;
            if(codec->codec_type == AVMEDIA_TYPE_VIDEO){
                av_stream_index = i;
            }
        }
        //2、根据视频流索引,获取解码器上下文
        AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
    
        //3、根据解码器上下文,获得解码器ID,然后查找解码器
        AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    
        //第五步:打开解码器
        int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
        if (avcodec_open2_result != 0){
            NSLog(@"打开解码器失败");
            return;
        }
        
        NSLog(@"测试编码器名称:%s",avcodec->name);
        
        
        //第六步:读取视频压缩数据->循环读取
        //1、分析av_read_frame参数
        //参数一:封装格式上下文
        //参数二:一帧压缩数据 = 一张图片
        //av_read_frame()
        //结构体大小计算:字节对齐原则
        AVPacket *packet = av_malloc(sizeof(AVPacket));
        //3.2 解码一帧视频压缩数据->进行解码(作用:用于解码操作)
        //开辟一块内存空间
        AVFrame * avframe_in = av_frame_alloc();
        
        int decode_result = 0;
        
        struct SwsContext *swsContext = sws_getContext(avcodec_context->width,
                                                       avcodec_context->height,
                                                       avcodec_context->pix_fmt,
                                                       avcodec_context->width,
                                                       avcodec_context->height,
                                                       AV_PIX_FMT_YUV420P,
                                                       SWS_BICUBIC,
                                                       NULL, NULL, NULL);
        
        //创建一个yuv420视频像素数据格式缓冲区(一帧数据)
        AVFrame *avframe_yuv_420p = av_frame_alloc();
        int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                                   avcodec_context->width,
                                                   avcodec_context->height,
                                                   1);
       
        //开辟一块内存空间
        uint8_t *out_buffer =(uint8_t *) av_malloc(buffer_size);
        //向avframe_yuv420p->填充数据
        //参数一:目标->填充数据(avframe_yuv420p)
        //参数二:目标->每一行大小
        //参数三:原始数据
        //参数四:目标->格式类型
        //参数五:宽
        //参数六:高
        //参数七:字节对齐方式
    
        av_image_fill_arrays(avframe_yuv_420p->data,
                             avframe_yuv_420p->linesize,
                             out_buffer, AV_PIX_FMT_YUV420P,
                             avcodec_context->width,
                             avcodec_context->height, 1);
        
        int y_size, u_size, v_size;
    
        FILE *file_yuv420p = fopen(outfilePath, "wb+");
        if(file_yuv420p == NULL){
            NSLog(@"输出文件打开失败");
            return;
        }
        int current_index = 0;
    
        while (av_read_frame(avformat_context, packet) >= 0) {
            //>=:读取到了
            //<0:读取错误或者读取完毕
            //2、是否是我们的视频流
            if(packet->stream_index == av_stream_index){
                //第七步:解码
                //学习一下C基础,结构体
                //3、解码一帧压缩数据->得到视频像素数据->yuv格式
                //采用新的API
                //3.1 发送一帧视频压缩数据
                avcodec_send_packet(avcodec_context, packet);
                decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
                if(decode_result == 0){
                    //解码成功
                    //4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式
                    //视频像素数据格式很多种类型: yuv420P、yuv422p、yuv444p等等...
                    //保证:我的解码后的视频像素数据格式统一为yuv420P->通用的格式
                    //进行类型转换: 将解码出来的视频像素点数据格式->统一转类型为yuv420P
                    //sws_scale作用:进行类型转换的
                    //参数一:视频像素数据格式上下文
                    //参数二:原来的视频像素数据格式->输入数据
                    //参数三:原来的视频像素数据格式->输入画面每一行大小
                    //参数四:原来的视频像素数据格式->输入画面每一行开始位置(填写:0->表示从原点开始读取)
                    //参数五:原来的视频像素数据格式->输入数据行数
                    //参数六:转换类型后视频像素数据格式->输出数据
                    //参数七:转换类型后视频像素数据格式->输出画面每一行大小
                    
                    sws_scale(swsContext,
                              (const uint8_t *const*)avframe_in->data,
                              avframe_in->linesize,
                              0,
                              avcodec_context->height,
                              avframe_yuv_420p->data,
                              avframe_yuv_420p->linesize);
                    
                    //方式一:直接显示视频上面去
                    //方式二:写入yuv文件格式
                    //5、将yuv420p数据写入.yuv文件中
                    //5.1 计算YUV大小
                    //分析一下原理?
                    //Y表示:亮度
                    //UV表示:色度
                    //有规律
                    //YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y)
                    //YUV420P格式规范二:4个像素点对应一个(U和V: 4Y = U = V)
                    
                    y_size = avcodec_context->width * avcodec_context->height;
                    u_size = y_size / 4;
                    v_size = u_size;
                    //ptr:指向保存数据的指针;
                    //size:每个数据类型的大小;
                    //count:数据的个数;
                    //stream:文件指针函数返回写入数据的个数
                    
                    fwrite(avframe_yuv_420p->data[0], 1, y_size, file_yuv420p);
                    fwrite(avframe_yuv_420p->data[1], 1, u_size, file_yuv420p);
                    fwrite(avframe_yuv_420p->data[2], 1, v_size, file_yuv420p);
                    current_index++;
                    NSLog(@"当前解码第%d帧", current_index);
                }
                
            }
        }
        
        av_packet_free(&packet);
        fclose(file_yuv420p);
        av_frame_free(&avframe_in);
        av_frame_free(&avframe_yuv_420p);
        free(out_buffer);
        avcodec_close(avcodec_context);
        avformat_free_context(avformat_context);
    }
    

    音频解码实现

    +(void)ffmepgDecodeAudio:(NSString*)jinfilePath outfilePath:(NSString *)joutfilePath{
        
        
        //第一步:组册组件
        const char *infilePath = [jinfilePath UTF8String];
        const char *outfilePath = [joutfilePath UTF8String];
        av_register_all();
        
        AVFormatContext *avformat_context = avformat_alloc_context();
        //第二步:打开封装格式->打开文件
        //参数一:封装格式上下文
        //作用:保存整个视频信息(解码器、编码器等等...)
        //信息:码率、帧率等...
        int avformat_open_input_result = avformat_open_input(&avformat_context,
                                                             infilePath,
                                                             NULL,
                                                             NULL);
        
        if (avformat_open_input_result != 0){
            NSLog(@"打开文件失败");
            return;
        }
        //第三步:查找视频流->拿到视频信息
        //参数一:封装格式上下文
        //参数二:指定默认配置
        int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,
                                                                         NULL);
        
        if (avformat_find_stream_info_result < 0){
            NSLog(@"查找失败");
            return;
        }
        //第四步:查找音频解码器
        //第一点:查找音频流索引位置
        int av_stream_index = -1;
        for (int i = 0; i < avformat_context->nb_streams; ++i) {
            //判断是否是音频流
            if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
                //AVMEDIA_TYPE_AUDIO->表示音频类型
                av_stream_index = i;
                break;
            }
        }
        
        //第二点:获取音频解码器上下文
        AVCodecContext * avcodec_context = avformat_context->streams[av_stream_index]->codec;
        
        //第三点:获得音频解码器
        AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
        if (avcodec == NULL){
            NSLog(@"查找音频解码器失败");
            return;
        }
        
        //第五步:打开音频解码器
        int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
        if (avcodec_open2_result != 0) {
            NSLog(@"打开音频解码器失败");
            return;
        }
        
        //第六步:读取音频压缩数据->循环读取
        //创建音频压缩数据帧
        //音频压缩数据->acc格式、mp3格式
        AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
        //创建音频采样数据帧
        AVFrame *frame_in = av_frame_alloc();
        
        //音频采样上下文->开辟了一快内存空间->pcm格式等...
        //设置参数
        //参数一:音频采样数据上下文
        //上下文:保存音频信息(记录)->目录
        SwrContext *swr_context = swr_alloc();
    
        //参数二:out_ch_layout->输出声道布局类型(立体声、环绕声、机器人等等...)
        //立体声
        int64_t out_ch_layout =AV_CH_LAYOUT_STEREO;
        //参数三:out_sample_fmt->输出采样精度->编码 AV_SAMPLE_FMT_S16
        enum AVSampleFormat out_sample_fmt =AV_SAMPLE_FMT_S16;
         //参数四:out_sample_rate->输出采样率(44100HZ)
        int out_sample_rate = 44100;
        //参数五:in_ch_layout->输入声道布局类型
        int64_t in_ch_layout = avcodec_context->channel_layout;
          //参数六:in_sample_fmt->输入采样精度
        enum AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
         //参数七:in_sample_rate->输入采样率
        int in_sample_rate = avcodec_context->sample_rate;
         //参数八:log_offset->log日志->从那里开始统计
        int log_offset = 0;
        //参数九:log_ctx->log上下文
        
        
        swr_alloc_set_opts(swr_context, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, log_offset, NULL);
        swr_init(swr_context);
        
        //输出缓冲区,
        //输出音频采样数据
        //缓冲区大小 = 采样率(44100HZ) * 采样精度(16位 = 2字节)
        int MAX_AUDIO_SIZE = 44100 * 2;
        uint8_t *out_buffer = (uint8_t *)malloc(sizeof(MAX_AUDIO_SIZE));
        
        FILE *out_file_pcm = fopen(outfilePath, "wb");
        if (out_file_pcm == NULL) {
            NSLog(@"输出文件打开失败");
            return;
        }
        
        int current_index = 0;
        while (av_read_frame(avformat_context, packet) >= 0) {
            //读取一帧音频压缩数据成功
            //判定是否是音频流
            if (packet->stream_index == av_stream_index) {
                //第七步:音频解码
                //1、发送一帧音频压缩数据包->音频压缩数据帧
                avcodec_send_packet(avcodec_context, packet);
                //2、解码一帧音频压缩数据包->得到->一帧音频采样数据->音频采样数据帧
                int avcodec_receive_result = avcodec_receive_frame(avcodec_context, frame_in);
                if (avcodec_receive_result == 0) {
                    //表示音频压缩数据解码成功
                    //3、类型转换(音频采样数据格式有很多种类型)
                    //我希望我们的音频采样数据格式->pcm格式->保证格式统一->输出PCM格式文件
                    //swr_convert:表示音频采样数据类型格式转换器
                    //参数一:音频采样数据上下文
                    //参数二:输出音频采样数据
                    //参数三:输出音频采样数据->大小
                    //参数四:输入音频采样数据
                    //参数五:输入音频采样数据->大小
                    swr_convert(swr_context, &out_buffer, MAX_AUDIO_SIZE, (const uint8_t **)frame_in->data, frame_in->nb_samples);
                    
                    //tian
                    //输出声道数量 av_get_channel_layout_nb_channels(out_ch_layout)
                    int out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);
                    int out_Buffer_size = av_samples_get_buffer_size(NULL,
                                                                     out_nb_channels,
                                                                     frame_in->nb_samples,
                                                                     out_sample_fmt, 1);
                    
                    fwrite(out_buffer, 1, out_Buffer_size, out_file_pcm);
                    current_index++;
                    NSLog(@"当前音频解码第%d帧",current_index);
    
                }
            }
        }
        //第八步:释放内存资源,关闭解码器
        av_packet_free(&packet);
        fclose(out_file_pcm);
        av_frame_free(&frame_in);
        free(out_buffer);
        
        avcodec_close(avcodec_context);
        avformat_free_context(avformat_context);
    }
    #音频解码实现
    
    +(void)ffmepgDecodeAudio:(NSString*)jinfilePath outfilePath:(NSString *)joutfilePath{
        
        
        //第一步:组册组件
        const char *infilePath = [jinfilePath UTF8String];
        const char *outfilePath = [joutfilePath UTF8String];
        av_register_all();
        
        AVFormatContext *avformat_context = avformat_alloc_context();
        //第二步:打开封装格式->打开文件
        //参数一:封装格式上下文
        //作用:保存整个视频信息(解码器、编码器等等...)
        //信息:码率、帧率等...
        int avformat_open_input_result = avformat_open_input(&avformat_context,
                                                             infilePath,
                                                             NULL,
                                                             NULL);
        
        if (avformat_open_input_result != 0){
            NSLog(@"打开文件失败");
            return;
        }
        //第三步:查找视频流->拿到视频信息
        //参数一:封装格式上下文
        //参数二:指定默认配置
        int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,
                                                                         NULL);
        
        if (avformat_find_stream_info_result < 0){
            NSLog(@"查找失败");
            return;
        }
        //第四步:查找音频解码器
        //第一点:查找音频流索引位置
        int av_stream_index = -1;
        for (int i = 0; i < avformat_context->nb_streams; ++i) {
            //判断是否是音频流
            if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
                //AVMEDIA_TYPE_AUDIO->表示音频类型
                av_stream_index = i;
                break;
            }
        }
        
        //第二点:获取音频解码器上下文
        AVCodecContext * avcodec_context = avformat_context->streams[av_stream_index]->codec;
        
        //第三点:获得音频解码器
        AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
        if (avcodec == NULL){
            NSLog(@"查找音频解码器失败");
            return;
        }
        
        //第五步:打开音频解码器
        int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
        if (avcodec_open2_result != 0) {
            NSLog(@"打开音频解码器失败");
            return;
        }
        
        //第六步:读取音频压缩数据->循环读取
        //创建音频压缩数据帧
        //音频压缩数据->acc格式、mp3格式
        AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
        //创建音频采样数据帧
        AVFrame *frame_in = av_frame_alloc();
        
        //音频采样上下文->开辟了一快内存空间->pcm格式等...
        //设置参数
        //参数一:音频采样数据上下文
        //上下文:保存音频信息(记录)->目录
        SwrContext *swr_context = swr_alloc();
    
        //参数二:out_ch_layout->输出声道布局类型(立体声、环绕声、机器人等等...)
        //立体声
        int64_t out_ch_layout =AV_CH_LAYOUT_STEREO;
        //参数三:out_sample_fmt->输出采样精度->编码 AV_SAMPLE_FMT_S16
        enum AVSampleFormat out_sample_fmt =AV_SAMPLE_FMT_S16;
         //参数四:out_sample_rate->输出采样率(44100HZ)
        int out_sample_rate = 44100;
        //参数五:in_ch_layout->输入声道布局类型
        int64_t in_ch_layout = avcodec_context->channel_layout;
          //参数六:in_sample_fmt->输入采样精度
        enum AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
         //参数七:in_sample_rate->输入采样率
        int in_sample_rate = avcodec_context->sample_rate;
         //参数八:log_offset->log日志->从那里开始统计
        int log_offset = 0;
        //参数九:log_ctx->log上下文
        
        
        swr_alloc_set_opts(swr_context, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, log_offset, NULL);
        swr_init(swr_context);
        
        //输出缓冲区,
        //输出音频采样数据
        //缓冲区大小 = 采样率(44100HZ) * 采样精度(16位 = 2字节)
        int MAX_AUDIO_SIZE = 44100 * 2;
        uint8_t *out_buffer = (uint8_t *)malloc(sizeof(MAX_AUDIO_SIZE));
        
        FILE *out_file_pcm = fopen(outfilePath, "wb");
        if (out_file_pcm == NULL) {
            NSLog(@"输出文件打开失败");
            return;
        }
        
        int current_index = 0;
        while (av_read_frame(avformat_context, packet) >= 0) {
            //读取一帧音频压缩数据成功
            //判定是否是音频流
            if (packet->stream_index == av_stream_index) {
                //第七步:音频解码
                //1、发送一帧音频压缩数据包->音频压缩数据帧
                avcodec_send_packet(avcodec_context, packet);
                //2、解码一帧音频压缩数据包->得到->一帧音频采样数据->音频采样数据帧
                int avcodec_receive_result = avcodec_receive_frame(avcodec_context, frame_in);
                if (avcodec_receive_result == 0) {
                    //表示音频压缩数据解码成功
                    //3、类型转换(音频采样数据格式有很多种类型)
                    //我希望我们的音频采样数据格式->pcm格式->保证格式统一->输出PCM格式文件
                    //swr_convert:表示音频采样数据类型格式转换器
                    //参数一:音频采样数据上下文
                    //参数二:输出音频采样数据
                    //参数三:输出音频采样数据->大小
                    //参数四:输入音频采样数据
                    //参数五:输入音频采样数据->大小
                    swr_convert(swr_context, &out_buffer, MAX_AUDIO_SIZE, (const uint8_t **)frame_in->data, frame_in->nb_samples);
                    
                    //tian
                    //输出声道数量 av_get_channel_layout_nb_channels(out_ch_layout)
                    int out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);
                    int out_Buffer_size = av_samples_get_buffer_size(NULL,
                                                                     out_nb_channels,
                                                                     frame_in->nb_samples,
                                                                     out_sample_fmt, 1);
                    
                    fwrite(out_buffer, 1, out_Buffer_size, out_file_pcm);
                    current_index++;
                    NSLog(@"当前音频解码第%d帧",current_index);
    
                }
            }
        }
        //第八步:释放内存资源,关闭解码器
        av_packet_free(&packet);
        fclose(out_file_pcm);
        av_frame_free(&frame_in);
        free(out_buffer);
        
        avcodec_close(avcodec_context);
        avformat_free_context(avformat_context);
    }
    

    在ViewController中调用

        NSString *inpath = [[NSBundle mainBundle] pathForResource:@"Test" ofType:@"mov"];
        NSString *outpath = [NSString stringWithFormat:@"%@/Documents/Test1.yuv",NSHomeDirectory()];
        
        NSFileManager *manager = [NSFileManager defaultManager];
        if(![manager fileExistsAtPath:outpath]){
           BOOL b= [manager createFileAtPath:outpath contents:nil attributes:nil];
            
            NSLog(@"%d",[manager fileExistsAtPath:outpath]);
        }else{
            
            long long l = [[manager attributesOfItemAtPath:outpath error:nil] fileSize];
            NSLog(@"%lld",l);
            NSLog(@"存在文件");
        }
    

    ffplay播放原始格式的音频文件(PCM)和视频文件(YUV)

    播放原始视频yuv数据, 以1280*720分辨率的xxx.yuv为例
    $ ffplay -f rawvideo -video_size 1280x720 xxx.yuv
    播放16kHz 1单声道 采样精度(编码)16bit的xxx.pcm的PCM文件为例
    $ ffplay -ar 16000 -channels 1 -f s16le -i xxx.pcm

    相关文章

      网友评论

          本文标题:FFMPEG-iOS-视频解码--简书

          本文链接:https://www.haomeiwen.com/subject/cjanvxtx.html