美文网首页ffmpeg
关于FFMPEG将NSData(h264)转换为UIImage的

关于FFMPEG将NSData(h264)转换为UIImage的

作者: 谢顶强 | 来源:发表于2020-10-06 20:34 被阅读0次

    FFMPEG将NSData(h264)转换为UIImage

    在实现了上一篇FFMPEG将NSData(h264)转换为UIImage的功能后,我找到了reading_io.c的文件,再次阅读了FFMPEG示例代码,修改了read_buffer的实现方式,解决了read_buffer函数重复执行的问题,同时增加了解编码器及上下文内存的回收操作,解决了大量执行转换操作时内存暴增的问题。

    1. 新增buffer_data结构体的定义(FFMPEG示例中可以直接找到)。我在使用时,将size参数修改为 flag标记,并在执行memcpy操作时,将flag标记为1。
    // FFMPEG示例
    struct buffer_data {
        uint8_t *ptr; // 传入bytes
        int size; // 传入size
    };
    // 我在适配项目后的改变
    struct buffer_data {
        uint8_t *ptr; // 传入bytes
        int flag; // 设置memcpy标记 0-未拷贝(默认)  1-已拷贝
    };
    
    1. 重新定义read_buffer函数,opaque将传入buffer_data结构体,不再直接传入bytes。并在执行memcpy前检测到flag,当flag==1时,return AVERROR_EOF;
    // FFMPEG示例
    int read_buffer(void *opaque, uint8_t *buf, int bufsize) {
        // 拷贝opaque 到buf
        struct buffer_data *bd = (struct buffer_data *)opaque;
        bufsize = FFMIN(bufsize, (int)bd->size);
        if (!bufsize) {
            return AVERROR_EOF;
        }
        printf("拷贝数据:%d\n", bufsize);
        memcpy(buf, bd->ptr, bufsize);
        bd->ptr += bufsize;
        bd->size -= bufsize;
        return bufsize;
    }
    // 我适配项目后的函数
    int read_buffer(void *opaque, uint8_t *buf, int bufsize) {
        // 拷贝opaque 到buf
        struct buffer_data *bd = (struct buffer_data *)opaque;
        if (bd->flag > 0) {
            return AVERROR_EOF;
        }
        printf("拷贝数据:%d\n", bufsize);
        memcpy(buf, bd->ptr, bufsize);
        bd->flag += 1;
        return bufsize;
    }
    
    1. 修改编解码器及上下文的回收操作
    + (BOOL) saveImageData:(NSData *)data toPath:(NSString *)path {
        // 读取io操作
        AVInputFormat *input_format = av_find_input_format("h264");
        // 事实上,在实际项目中,我们不需要判断input_format是否发现成功。
        // 因为开发者理应清楚自己的项目是否支持该输入类型
        if (!input_format) {
            return NO;
        }
        // 申请buffer所需要的内存
        unsigned char *input_buffer = (unsigned char *)av_mallocz(data.length);
        // 初始化结构体buffer_data,并将NSData的信息存入到结构体中
        struct buffer_data bd = { 0 };
        bd.ptr  = (uint8_t *)data.bytes;
        bd.size = (int)data.length;
        // 初始化io上下文,并将结构体bd作为opaque传入
        /* 关于avio_alloc_context的参数,网络上已经存在很清晰的描述
         * 我们也不需要特别分析此处的内容
         */
        AVIOContext *avio_input = avio_alloc_context(input_buffer, (int)data.length, 0, &bd, &read_buffer, NULL, NULL);
        // 初始化AVFormatContext,并将io赋值为pb
        AVFormatContext *input_format_context = avformat_alloc_context();
        input_format_context->pb = avio_input;
        input_format_context->flags = AVFMT_FLAG_CUSTOM_IO;
        // 打开输入上下文
        int err = avformat_open_input(&input_format_context, NULL, input_format, NULL);
        if (err < 0) {
            goto end;
        }
        // 获取输入源信息
        err = avformat_find_stream_info(input_format_context, NULL);
        if (err < 0) {
            goto end;
        }
        // 本次的数据源只有一帧h264数据,可以确定数据类型,因此取消了数据类型的判断操作
        AVStream *stream = input_format_context->streams[0];
        AVFrame *originFrame = av_frame_alloc();
        BOOL isSuc = [self decodeImage:input_format_context codecContext:stream->codecpar frame:originFrame];
        if (!isSuc) {
            av_frame_free(&originFrame);
            err = -1;
            goto end;
        }
        // 由于我们要存储的是二进制数据,所以要用wb的方式打开文件
        FILE *file = fopen([path UTF8String], "wb");
        // 图片数据重新编码,并将编码数据写入文件中
        isSuc = [self encodeImage:originFrame file:file];
        // 处理完毕后,必须关闭文件
        fclose(file);
        // 释放frame
        av_frame_free(&originFrame);
         if (!isSuc) {
            err = -1;
            goto end;
         }
    end:
        if (input_format_context) {
            avformat_close_input(&input_format_context);
            input_format_context = nil;
        }
        if (avio_input) {
            av_free(avio_input->buffer);
            avio_context_free(&avio_input);
            avio_input = NULL;
        }
        return err >= 0;
    }
    
    + (BOOL) decodeImage:(AVFormatContext *)formatContext codecContext:(AVCodecParameters *)parameters frame:(AVFrame *)frame {
        int err = 0;
        AVPacket *packet = NULL;
        // 创建指定类型的编码器
        AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
        // 创建编码器上下文
        AVCodecContext *codecContext = avcodec_alloc_context3(codec);
        // 拷贝参数到上下文中
        err = avcodec_parameters_to_context(codecContext, parameters);
        // 打开上下文获取信息
        err = avcodec_open2(codecContext, codec, NULL);
        if (err < 0) {
            goto end;
        }
        // 创建数据包
        packet = av_packet_alloc();
        // 初始化数据包
        av_init_packet(packet);
        // 读取frame到包中
        err = av_read_frame(formatContext, packet);
        if (err < 0) {
            goto end;
        }
        // 发送包到上下文
        err = avcodec_send_packet(codecContext, packet);
        if (err < 0) {
            goto end;
        }
        // 从上下文中接收frame
        err = avcodec_receive_frame(codecContext, frame);
        if (err < 0) {
            goto end;
        }
    end:
        // 释放packet
        if (packet) {
            av_packet_free(&packet);
            packet = NULL;
        }
        // 关闭上下文
        avcodec_close(codecContext);
        return YES;
    }
    
    + (BOOL) encodeImage:(AVFrame *)frame file:(FILE *)file {
        AVPacket *packet = NULL;
        int err;
        // 创建图片编码器
        AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
        // 创建上下文
        AVCodecContext *codec_context = avcodec_alloc_context3(encoder);
        codec_context->width = frame->width;
        codec_context->height = frame->height;
        codec_context->time_base.num = 1;
        codec_context->time_base.den = 1000;
        codec_context->pix_fmt = AV_PIX_FMT_YUVJ420P;
        codec_context->codec_id = AV_CODEC_ID_MJPEG;
        codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
        // 打开上下文
        err = avcodec_open2(codec_context, encoder, NULL);
        if (err < 0) {
            goto end;
        }
        // 发送frame到上下文
        err = avcodec_send_frame(codec_context, frame);
        if (err < 0) {
            goto end;
        }
        // 初始化接收packet
        packet = av_packet_alloc();
        av_init_packet(packet);
        // 开始从上下文接收packet
        err = avcodec_receive_packet(codec_context, packet);
        if (err < 0) {
            goto end;
        }
        // 数据已接收完成
        // 此时可以将数据写入本地文件中,也可以直接转换为NSData数据使用
        // 生成的NSData可直接用于创建UIImage
        // 为了保证现有项目架构不再发生变化,我是将packet->data直接写入本地文件
        /*
         NSLog(@"重编码结果:%d", packet->size);
         uint8_t *data = packet->data;
         NSData *imageData = [NSData dataWithBytes:(const void *)data length:packet->size];
         NSLog(@"转码后数据:%@", imageData);
         UIImage *image = [UIImage imageWithData:imageData];
         NSLog(@"转码后图片:%@", image);
         */
        // 写入数据
        fwrite(packet->data, packet->size, 1, file);
        // 刷流
        fflush(file);
        
    end:
        // 释放packet数据
        if (packet) {
            av_packet_free(&packet);
            packet = NULL;
        }
        // 关闭上下文
        avcodec_close(codec_context);
        // 释放上下文数据
        if (codec_context) {
            avcodec_free_context(&codec_context);
        }
        return err >= 0;
    }
    

    相关文章

      网友评论

        本文标题:关于FFMPEG将NSData(h264)转换为UIImage的

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