通过ffmpegwrapper剪切MPEG-TS文件?

我在设备上有MPEG-TS文件。 我想在设备上的文件的开始削减一个相当​​准确的时间。

使用FFmpegWrapper作为基础,我希望实现这一点。

然而,我在ffmpeg的C API上有些遗憾。 我从哪说起呢?

我试图在我开始寻找PTS之前丢弃所有数据包,但这打破了videostream。

packet->pts = av_rescale_q(packet->pts, inputStream.stream->time_base, outputStream.stream->time_base); packet->dts = av_rescale_q(packet->dts, inputStream.stream->time_base, outputStream.stream->time_base); if(startPts == 0){ startPts = packet->pts; } if(packet->pts < cutTimeStartPts + startPts){ av_free_packet(packet); continue; } 

如何在不破坏videostream的情况下切断部分input文件的开始? 当回放时,我想要2个切分段一起无缝运行。

 ffmpeg -i time.ts -c:v libx264 -c:a copy -ss $CUT_POINT -map 0 -y after.ts ffmpeg -i time.ts -c:v libx264 -c:a copy -to $CUT_POINT -map 0 -y before.ts 

似乎是我所需要的。 我认为重新编码是必要的,所以video可以在任意点开始,而不是现有的关键帧。 如果有更有效的解决scheme,那太好了。 如果没有,这是够好的。

编辑:这是我的尝试。 我正在拼凑各种我不完全明白从这里复制的作品。 现在我要离开“剪辑”部分,尝试获取audio+video编码,而不需要分层复杂。 我在avcodec_encode_video2(...)上得到EXC_BAD_ACCESS

 - (void)convertInputPath:(NSString *)inputPath outputPath:(NSString *)outputPath options:(NSDictionary *)options progressBlock:(FFmpegWrapperProgressBlock)progressBlock completionBlock:(FFmpegWrapperCompletionBlock)completionBlock { dispatch_async(conversionQueue, ^{ FFInputFile *inputFile = nil; FFOutputFile *outputFile = nil; NSError *error = nil; inputFile = [[FFInputFile alloc] initWithPath:inputPath options:options]; outputFile = [[FFOutputFile alloc] initWithPath:outputPath options:options]; [self setupDirectStreamCopyFromInputFile:inputFile outputFile:outputFile]; if (![outputFile openFileForWritingWithError:&error]) { [self finishWithSuccess:NO error:error completionBlock:completionBlock]; return; } if (![outputFile writeHeaderWithError:&error]) { [self finishWithSuccess:NO error:error completionBlock:completionBlock]; return; } AVRational default_timebase; default_timebase.num = 1; default_timebase.den = AV_TIME_BASE; FFStream *outputVideoStream = outputFile.streams[0]; FFStream *inputVideoStream = inputFile.streams[0]; AVFrame *frame; AVPacket inPacket, outPacket; frame = avcodec_alloc_frame(); av_init_packet(&inPacket); while (av_read_frame(inputFile.formatContext, &inPacket) >= 0) { if (inPacket.stream_index == 0) { int frameFinished; avcodec_decode_video2(inputVideoStream.stream->codec, frame, &frameFinished, &inPacket); // if (frameFinished && frame->pkt_pts >= starttime_int64 && frame->pkt_pts <= endtime_int64) { if (frameFinished){ av_init_packet(&outPacket); int output; avcodec_encode_video2(outputVideoStream.stream->codec, &outPacket, frame, &output); if (output) { if (av_write_frame(outputFile.formatContext, &outPacket) != 0) { fprintf(stderr, "convert(): error while writing video frame\n"); [self finishWithSuccess:NO error:nil completionBlock:completionBlock]; } } av_free_packet(&outPacket); } if (frame->pkt_pts > endtime_int64) { break; } } } av_free_packet(&inPacket); if (![outputFile writeTrailerWithError:&error]) { [self finishWithSuccess:NO error:error completionBlock:completionBlock]; return; } [self finishWithSuccess:YES error:nil completionBlock:completionBlock]; }); } 

FFmpeg(libavformat / codec,在这种情况下)API非常紧密地映射ffmpeg.exe命令行参数。 要打开文件,请使用avformat_open_input_file ()。 最后两个参数可以是NULL。 这为你填写AVFormatContext。 现在你开始在循环中使用av_read_frame()来读取帧。 pkt.stream_index会告诉你每个数据包属于哪个数据stream,而avformatcontext-> streams [pkt.stream_index]是伴随的数据stream信息,告诉你它使用什么编解码器,是video还是audio等。使用avformat_close()去关机。

对于muxing,你使用反转,看到muxing的细节。 基本上,它分配 , avio_open2 ,为input文件(基本上context- > streams []), avformat_write_header() , av_interleaved_write_frame()在一个循环, av_write_trailer()closures(并释放分配的上下文结束)。

videostream的编码/解码使用libavcodec完成。 对于从复用器获得的每个AVPacket,使用avcodec_decode_video2() 。 使用avcodec_encode_video2()来编码输出AVFrame。 请注意,两者都会引入延迟,因此对每个函数的前几个调用都不会返回任何数据,您需要通过使用NULLinput数据调用每个函数来清空caching的数据,以获取尾部数据包/帧。 av_interleave_write_frame将正确地交错数据包,使得video/audiostream不会失步(例如,在ts文件中的audio数据包之后,具有相同时间戳的video数据包发生MB)。

如果您需要avcodec_decode_video2,avcodec_encode_video2,av_read_frame或av_interleaved_write_frame更详细的示例,只需Google“$函数示例”,您将看到完整的示例,展示如何正确使用它们。 对于x264编码,在为编码质量设置调用avcodec_open2时,在AVCodecContext中设置一些默认参数 。 在C API中,你使用AVDictionary来做到这一点 ,例如:

 AVDictionary opts = *NULL; av_dict_set(&opts, "preset", "veryslow", 0); // use either crf or b, not both! See the link above on H264 encoding options av_dict_set_int(&opts, "b", 1000, 0); av_dict_set_int(&opts, "crf", 10, 0); 

哦,我忘了一个部分,时间戳。 每个AVPacket和AVFrame在其结构中都有一个ptsvariables,您可以使用它来决定是否将数据包/帧包含在输出stream中。 因此,对于audio,您将使用多路分解步骤中的AVPacket.pts作为分隔符;对于video,您可以使用解码步骤中的AVFrame.pts作为分隔符。 他们各自的文件告诉你他们在什么单位。

我看到你仍然有一些没有实际代码的问题,所以这里是一个真正的(工作)代码转换器,它重新编码video和重新复制audio。 它可能有大量的错误,漏洞和缺乏适当的错误报告,它也没有处理时间戳(我把它留给你作为一个练习),但它所做的基本的事情,你要求:

 #include <stdio.h> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> static AVFormatContext *inctx, *outctx; #define MAX_STREAMS 16 static AVCodecContext *inavctx[MAX_STREAMS]; static AVCodecContext *outavctx[MAX_STREAMS]; static int openInputFile(const char *file) { int res; inctx = NULL; res = avformat_open_input(& inctx, file, NULL, NULL); if (res != 0) return res; res = avformat_find_stream_info(inctx, NULL); if (res < 0) return res; return 0; } static void closeInputFile(void) { int n; for (n = 0; n < inctx->nb_streams; n++) if (inavctx[n]) { avcodec_close(inavctx[n]); avcodec_free_context(&inavctx[n]); } avformat_close_input(&inctx); } static int openOutputFile(const char *file) { int res, n; outctx = avformat_alloc_context(); outctx->oformat = av_guess_format(NULL, file, NULL); if ((res = avio_open2(&outctx->pb, file, AVIO_FLAG_WRITE, NULL, NULL)) < 0) return res; for (n = 0; n < inctx->nb_streams; n++) { AVStream *inst = inctx->streams[n]; AVCodecContext *inc = inst->codec; if (inc->codec_type == AVMEDIA_TYPE_VIDEO) { // video decoder inavctx[n] = avcodec_alloc_context3(inc->codec); avcodec_copy_context(inavctx[n], inc); if ((res = avcodec_open2(inavctx[n], avcodec_find_decoder(inc->codec_id), NULL)) < 0) return res; // video encoder AVCodec *encoder = avcodec_find_encoder_by_name("libx264"); AVStream *outst = avformat_new_stream(outctx, encoder); outst->codec->width = inavctx[n]->width; outst->codec->height = inavctx[n]->height; outst->codec->pix_fmt = inavctx[n]->pix_fmt; AVDictionary *dict = NULL; av_dict_set(&dict, "preset", "veryslow", 0); av_dict_set_int(&dict, "crf", 10, 0); outavctx[n] = avcodec_alloc_context3(encoder); avcodec_copy_context(outavctx[n], outst->codec); if ((res = avcodec_open2(outavctx[n], encoder, &dict)) < 0) return res; } else if (inc->codec_type == AVMEDIA_TYPE_AUDIO) { avformat_new_stream(outctx, inc->codec); inavctx[n] = outavctx[n] = NULL; } else { fprintf(stderr, "Don't know what to do with stream %d\n", n); return -1; } } if ((res = avformat_write_header(outctx, NULL)) < 0) return res; return 0; } static void closeOutputFile(void) { int n; av_write_trailer(outctx); for (n = 0; n < outctx->nb_streams; n++) if (outctx->streams[n]->codec) avcodec_close(outctx->streams[n]->codec); avformat_free_context(outctx); } static int encodeFrame(int stream_index, AVFrame *frame, int *gotOutput) { AVPacket outPacket; int res; av_init_packet(&outPacket); if ((res = avcodec_encode_video2(outavctx[stream_index], &outPacket, frame, gotOutput)) < 0) { fprintf(stderr, "Failed to encode frame\n"); return res; } if (*gotOutput) { outPacket.stream_index = stream_index; if ((res = av_interleaved_write_frame(outctx, &outPacket)) < 0) { fprintf(stderr, "Failed to write packet\n"); return res; } } av_free_packet(&outPacket); return 0; } static int decodePacket(int stream_index, AVPacket *pkt, AVFrame *frame, int *frameFinished) { int res; if ((res = avcodec_decode_video2(inavctx[stream_index], frame, frameFinished, pkt)) < 0) { fprintf(stderr, "Failed to decode frame\n"); return res; } if (*frameFinished){ int hasOutput; frame->pts = frame->pkt_pts; return encodeFrame(stream_index, frame, &hasOutput); } else { return 0; } } int main(int argc, char *argv[]) { char *input = argv[1]; char *output = argv[2]; int res, n; printf("Converting %s to %s\n", input, output); av_register_all(); if ((res = openInputFile(input)) < 0) { fprintf(stderr, "Failed to open input file %s\n", input); return res; } if ((res = openOutputFile(output)) < 0) { fprintf(stderr, "Failed to open output file %s\n", input); return res; } AVFrame *frame = av_frame_alloc(); AVPacket inPacket; av_init_packet(&inPacket); while (av_read_frame(inctx, &inPacket) >= 0) { if (inavctx[inPacket.stream_index] != NULL) { int frameFinished; if ((res = decodePacket(inPacket.stream_index, &inPacket, frame, &frameFinished)) < 0) { return res; } } else { if ((res = av_interleaved_write_frame(outctx, &inPacket)) < 0) { fprintf(stderr, "Failed to write packet\n"); return res; } } } for (n = 0; n < inctx->nb_streams; n++) { if (inavctx[n]) { // flush decoder int frameFinished; do { inPacket.data = NULL; inPacket.size = 0; if ((res = decodePacket(n, &inPacket, frame, &frameFinished)) < 0) return res; } while (frameFinished); // flush encoder int gotOutput; do { if ((res = encodeFrame(n, NULL, &gotOutput)) < 0) return res; } while (gotOutput); } } av_free_packet(&inPacket); closeInputFile(); closeOutputFile(); return 0; } 

看看这个问题的答案。

基本上简而言之,你可以使用:

 ffmpeg -i time.ts -c:v libx264 -c:a copy -ss $CUT_POINT -map 0 -y after.ts ffmpeg -i time.ts -c:v libx264 -c:a copy -to $CUT_POINT -map 0 -y before.ts 

只是作为参考,这个问题被接受的答案是:

如何在保留所有音轨的同时使用ffmpeg分割和join文件?

正如您已经发现的,根据stream规范文档 ,比特stream副本将只select一个 (audio)轨道:

默认情况下, ffmpeg只包含input文件中每种types(video,audio,字幕)的一个stream,并将其添加到每个输出文件中。 它根据以下标准挑选每一个的“最佳”:对于video,它是具有最高分辨率的stream,对于audio,它是具有最多频道的stream,对于字幕,是第一个字幕stream。 在相同types的几个stream相同的情况下,select具有最低索引的stream。

要select所有音轨:

 ffmpeg -i InputFile.ts-c copy -ss 00:12:34.567 -t 00:34:56.789 -map 0:v -map 0:a FirstFile.ts 

要select第三个音轨:

 ffmpeg -i InputFile.ts -c copy -ss 00:12:34.567 -t 00:34:56.789 -map 0:v -map 0:a:2 FirstFile.ts 

您可以在ffmpeg文档的高级选项部分阅读更多关于和查看streamselect的其他示例。

我也将-vcodec copy -acodec copy从你原来的命令-c copy-c copy ,如上所述为了expression的紧凑性。

分裂:

所以,把这些与你想要在两个文件中实现的分割结合起来,以便以后重新join:

 ffmpeg -i InputOne.ts -ss 00:02:00.0 -c copy -map 0:v -map 0:a OutputOne.ts ffmpeg -i InputTwo.ts -c copy -t 00:03:05.0 -map 0:v -map 0:a OutputTwo.ts 

会给你:

  • OutputOne.ts ,这是第一个input文件的前两分钟的所有内容
  • OutputTwo.ts ,这是第二个input文件的 3分5秒

join:

ffmpeg支持文件串联而不需要重新编码, 在连接文档中进行了广泛描述 。

创build您要join的文件列表(例如join.txt ):

 file '/path/to/files/OutputOne.ts' file '/path/to/files/OutputTwo.ts' 

然后你的ffmpeg命令可以使用concat demuxer

  ffmpeg -f concat -i join.txt -c copy FinalOutput.ts 

由于您正在使用mpeg传输stream( .ts ),因此您应该也可以使用concat 协议

 ffmpeg -i "concat:OutputOne.ts|OutputTwo.ts" -c copy -bsf:a aac_adtstoasc output.mp4 

根据上面链接的concat页面上的示例。 我会留给你试验一下。