美文网首页
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