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

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的更新)