1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
| /** 他山之石,学习为主,版权所无,翻版不究,有错无责 Late Lee 2015.08 基于内存的格式封装测试(从内存视频转换到另一片内存视频) 使用 ./a.out a.avi a.mkv
支持的: avi mkv mp4 flv ts ...
参考: http://blog.csdn.net/leixiaohua1020/article/details/25422685
log 新版本出现: Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
test passed!!
mp4->avi failed 出现: H.264 bitstream malformed, no startcode found, use the h264_mp4toannexb bitstream filter 解决见: http://blog.chinaunix.net/uid-11344913-id-4432752.html 官方解释: https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
ts -> avi passed
其它:
1、传递给ffmpeg的avio_alloc_context中的内存p和大小size,可以使用32768。 如果转换后的数据保存在内存p1,这个内存p1一定要和前面所说的p不同。因为 在自定义的write中的buf参数,就是p,所以要拷贝到其它内存。 如定义p为32768,但定义p1为50MB,可以转换50MB的视频 测试: p为32768时,需调用write 1351次 2倍大小时,调用write 679次 p越大,调用次数最少,内存消耗越大 (用time测试,时间上没什么变化,前者为4.7s,后者为4.6s,但理论上内存大一点好)
2、优化: 转换功能接口封装为类,把write、seek等和内存有关的操作放到类外部实现, 再传递到该类中,该类没有内存管理更好一些。
todo 重复读文件,如何做? */
#include #include #include #include #include #include extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" }
#include "file_utils.h"
#ifndef min #define min(a,b) ((a) > (b) ? (b) : (a)) #endif
#define _LL_DEBUG_
// low level debug #ifdef _LL_DEBUG_ #define debug(fmt, ...) printf(fmt, ##__VA_ARGS__) #define LL_DEBUG(fmt, ...) printf("[DEBUG %s().%d @ %s]: " fmt, \ __func__, __LINE__, P_SRC, ##__VA_ARGS__) #else #define debug(fmt, ...) #define LL_DEBUG(fmt, ...) #endif
#define DEFAULT_MEM (10*1024*1024)
//参考file协议的内存,使用大小32768,大一点也可以 #define IO_BUFFER_SIZE (32768*1)
typedef struct AVIOBufferContext { unsigned char* ptr; int pos; int totalSize; int realSize; }AVIOBufferContext;
// note 这两个是用户视频数据, // g_avbuffer_in为已经读取的视频 // g_avbuffer_out是ffmpeg转换后的视频,直接将该内存写入文件即可 AVIOBufferContext g_avbuffer_in; AVIOBufferContext g_avbuffer_out;
// note这两个是FFMPEG内部使用的IO内存,与AVIOBufferContext的ptr不同 // 在测试时,发现直接定义为数组,会有错误,故使用malloc static char *g_ptr_in = NULL; static char *g_ptr_out = NULL;
// 每次read_frame时,就会调用到这个函数,该函数从g_avbuffer_in读数据 static int my_read(void *opaque, unsigned char *buf, int size) { AVIOBufferContext* op = (AVIOBufferContext*)opaque; int len = size; if (op->pos + size > op->totalSize) { len = op->totalSize - op->pos; } memcpy(buf, op->ptr + op->pos, len); if (op->pos + len >= op->realSize) op->realSize += len; op->pos += len;
return len; }
static int my_write(void *opaque, unsigned char *buf, int size) { AVIOBufferContext* op = (AVIOBufferContext*)opaque; if (op->pos + size > op->totalSize) { // 重新申请 // 根据数值逐步加大 int newTotalLen = op->totalSize*sizeof(char) * 3 / 2; unsigned char* ptr = (unsigned char*)av_realloc(op->ptr, newTotalLen); if (ptr == NULL) { // todo 是否在此处释放内存? return -1; } debug("org ptr: %p new ptr: %p size: %d(%0.fMB) ", op->ptr, ptr, newTotalLen, newTotalLen/1024.0/1024.0); op->totalSize = newTotalLen; op->ptr = ptr; debug(" realloc!!!!!!!!!!!!!!!!!!!!!!!\n"); } memcpy(op->ptr + op->pos, buf, size);
if (op->pos + size >= op->realSize) op->realSize += size;
//static int cnt = 1; //debug("%d write %p %p pos: %d len: %d\n", cnt++, op->ptr, buf, op->pos, size); op->pos += size;
return 0; }
static int64_t my_seek(void *opaque, int64_t offset, int whence) { AVIOBufferContext* op = (AVIOBufferContext*)opaque; int64_t new_pos = 0; // 可以为负数 int64_t fake_pos = 0;
switch (whence) { case SEEK_SET: new_pos = offset; break; case SEEK_CUR: new_pos = op->pos + offset; break; case SEEK_END: // 此处可能有问题 new_pos = op->totalSize + offset; break; default: return -1; } fake_pos = min(new_pos, op->totalSize); if (fake_pos != op->pos) { op->pos = fake_pos; } //debug("seek pos: %d(%d)\n", offset, op->pos); return new_pos; }
int remuxer_mem_read(int argc, char* argv[]) { //输入对应一个AVFormatContext,输出对应一个AVFormatContext AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL; AVIOContext *avio_in = NULL, *avio_out = NULL; const char *in_filename = NULL, *out_filename = NULL; AVPacket pkt; int ret = 0;
if (argc < 3) { printf("usage: %s [input file] [output file]\n", argv[0]); printf("eg %s foo.avi bar.ts\n", argv[0]); return -1; }
in_filename = argv[1]; out_filename = argv[2];
memset(&g_avbuffer_in, '\0', sizeof(AVIOBufferContext)); memset(&g_avbuffer_out, '\0', sizeof(AVIOBufferContext));
read_file(in_filename, (char**)&g_avbuffer_in.ptr, &g_avbuffer_in.totalSize); // 分配输出视频数据空间 g_avbuffer_out.ptr = (unsigned char*)av_realloc(NULL, DEFAULT_MEM*sizeof(char)); // new if (g_avbuffer_out.ptr == NULL) { debug("alloc output mem failed.\n"); return -1; } g_avbuffer_out.totalSize = DEFAULT_MEM; memset(g_avbuffer_out.ptr, '\0', g_avbuffer_out.totalSize); g_ptr_in = (char*)malloc(IO_BUFFER_SIZE*sizeof(char)); g_ptr_out = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));
// 初始化 av_register_all();
// 输出相关 // note 要指定IO内存,还在指定自定义的操作函数,这里有write和seek avio_out =avio_alloc_context((unsigned char *)g_ptr_out, IO_BUFFER_SIZE, 1, &g_avbuffer_out, NULL, my_write, my_seek); if (!avio_out) { printf( "avio_alloc_context failed\n"); ret = AVERROR_UNKNOWN; goto end; } // 分配AVFormatContext // 为方便起见,使用out_filename来根据输出文件扩展名来判断格式 // 如果要使用如“avi”、“mp4”等指定,赋值给第3个参数即可 // 注意该函数会分配AVOutputFormat avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename); if (!ofmt_ctx) { printf( "Could not create output context\n"); ret = AVERROR_UNKNOWN; goto end; } ofmt_ctx->pb=avio_out; // 赋值自定义的IO结构体 ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定为自定义
debug("guess format: %s(%s) flag: %d\n", ofmt_ctx->oformat->name, ofmt_ctx->oformat->long_name, ofmt_ctx->oformat->flags);
// 输入相关 // 分配自定义的AVIOContext 要区别于输出的buffer // 由于数据已经在内存中,所以指定read即可,不用write和seek avio_in =avio_alloc_context((unsigned char *)g_ptr_in, IO_BUFFER_SIZE, 0, &g_avbuffer_in, my_read, NULL, NULL); if (!avio_in) { printf( "avio_alloc_context for input failed\n"); ret = AVERROR_UNKNOWN; goto end; } // 分配输入的AVFormatContext ifmt_ctx=avformat_alloc_context(); if (!ifmt_ctx) { printf( "Could not create output context\n"); ret = AVERROR_UNKNOWN; goto end; } ifmt_ctx->pb=avio_in; // 赋值自定义的IO结构体 ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定为自定义
// 注:第二个参数本来是文件名,但基于内存,不再有意义,随便用字符串 if ((ret = avformat_open_input(&ifmt_ctx, "wtf", NULL, NULL)) < 0) { printf("Cannot open input file\n"); return ret; } if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { printf("Cannot find stream information\n"); return ret; }
// 复制所有的stream for (int i = 0; i < (int)(ifmt_ctx->nb_streams); i++) { //根据输入流创建输出流 AVStream *in_stream = ifmt_ctx->streams[i]; AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec); if (!out_stream) { printf( "Failed allocating output stream\n"); ret = AVERROR_UNKNOWN; goto end; } //复制AVCodecContext的设置 ret = avcodec_copy_context(out_stream->codec, in_stream->codec); if (ret < 0) { printf( "Failed to copy context from input to output stream codec context\n"); goto end; } out_stream->codec->codec_tag = 0; if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; } //输出一下格式------------------ printf("output format:\n"); av_dump_format(ofmt_ctx, 0, out_filename, 1);
// 写文件头 ret = avformat_write_header(ofmt_ctx, NULL); if (ret < 0) { printf( "Error occurred when opening output file\n"); goto end; }
// 帧 while (1) { AVStream *in_stream, *out_stream; //获取一个AVPacket ret = av_read_frame(ifmt_ctx, &pkt); if (ret < 0) { printf("av_read_frame failed or end of stream.\n"); break; } in_stream = ifmt_ctx->streams[pkt.stream_index]; out_stream = ofmt_ctx->streams[pkt.stream_index];
//转换PTS/DTS pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base); pkt.pos = -1;
// 写入一帧 ret = av_interleaved_write_frame(ofmt_ctx, &pkt); if (ret < 0) { printf( "Error muxing packet\n"); break; } av_free_packet(&pkt); }
//写文件尾(Write file trailer) printf("--------write trailer------------\n"); av_write_trailer(ofmt_ctx);
// 把输出的视频写到文件中 printf("write to file: %s %p %d\n", out_filename, g_avbuffer_out.ptr, g_avbuffer_out.realSize); write_file(out_filename, (char*)g_avbuffer_out.ptr, g_avbuffer_out.realSize, 1);
end: if (avio_in != NULL) av_freep(avio_in); if (avio_out != NULL) av_freep(avio_out); //if (g_ptr_in != NULL) free(g_ptr_in); if (g_ptr_out != NULL) free(g_ptr_out);
// 该函数会释放用户自定义的IO buffer,上面不再释放,否则会corrupted double-linked list avformat_close_input(&ifmt_ctx); avformat_free_context(ofmt_ctx);
if (g_avbuffer_in.ptr != NULL) free(g_avbuffer_in.ptr); if (g_avbuffer_out.ptr != NULL) free(g_avbuffer_out.ptr);
return ret; }
|