通过 ffmpegwrapper 切割 MPEG-TS 文件?
Posted
技术标签:
【中文标题】通过 ffmpegwrapper 切割 MPEG-TS 文件?【英文标题】:Cutting MPEG-TS file via ffmpegwrapper? 【发布时间】:2016-04-21 18:48:07 【问题描述】:我的设备上有 MPEG-TS 文件。我想缩短设备上文件的开始时间。
以FFmpegWrapper 为基础,我希望能够实现这一目标。
不过,我对 ffmpeg 的 C API 有点迷茫。我从哪里开始?
我尝试在我正在寻找的开始 PTS 之前丢弃所有数据包,但这破坏了视频流。
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;
如何在不破坏视频流的情况下切断输入文件的部分开头?背靠背播放时,我希望 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
似乎是我需要的。我认为需要重新编码,以便视频可以从任意点开始,而不是现有的关键帧。如果有更有效的解决方案,那就太好了。如果没有,这就足够了。
编辑:这是我的尝试。我正在拼凑从here复制的各种我不完全理解的部分。我暂时放弃“剪辑”部分,尝试在没有分层复杂性的情况下编写音频+视频编码。我在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];
);
【问题讨论】:
所以现在我要添加一个答案。如果没有更有效的答案,我希望我能得到赏金, 你应该只剪切随机访问点(I 帧),这样你就可以搜索最近的 PTS,在那里你还可以找到一个随机访问指示符(我认为它被称为 RAP 标志,你应该寻找它在标题中)如果您想每隔一帧剪切,那么您只能通过对视频进行转码来做到这一点 @StefanKendall,是我的回答好吧。你觉得还有什么需要补充的吗?另外,如果您找到更有效的方法,请随时通知我。 @StefanKendall,另外,我删除了我提到链接的评论,因为我在答案中添加了链接。 @Stefan Kendall,您应该将-ss
移动到 -i
之前,否则 ffmpeg 将解码所有帧然后丢弃,而不是寻求 inpoint。
【参考方案1】:
查看this问题的接受答案。
简而言之,你可以使用:
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
拆分和合并文件?
正如您所发现的,比特流副本将仅选择 一个(音频)轨道,根据 stream specification documentation:
默认情况下,
ffmpeg
仅包含输入文件中存在的每种类型(视频、音频、字幕)的一个流,并将它们添加到每个输出文件中。它根据以下标准选择每个“最佳”:对于视频,它是具有最高分辨率的流,对于音频,它是具有最多频道的流,对于字幕,它是第一个字幕流。在相同类型的多个流速率相同的情况下,选择索引最低的流。
选择所有个音轨:
ffmpeg -i InputFile.ts-c copy -ss 00:12:34.567 -t 00:34:56.789 -map 0:v -map 0:a FirstFile.ts
选择第三个音轨:
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
文档的advanced options 部分阅读更多信息并查看其他流选择示例。
我还将把原始命令中的 -vcodec copy -acodec copy
合并到上面的 -c copy
中,以使表达更简洁。
拆分:
因此,将它们与您希望在两个文件中实现的内容结合起来,以便稍后重新加入:
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
,这是在第一个输入文件的前两分钟之后的所有内容
OutputTwo.ts
,也就是第一个第二个输入文件的3分5秒
加入:
ffmpeg
支持不重新编码的文件串联,described extensively in its concatenation documentation。
创建要加入的文件列表(例如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
传输流 (.ts
),您应该也可以使用 concat 协议:
ffmpeg -i "concat:OutputOne.ts|OutputTwo.ts" -c copy -bsf:a aac_adtstoasc output.mp4
根据上面链接的 concat 页面上的示例。我将把它留给你来试验。
【讨论】:
是的,那么我该如何通过 ffmpeg C api 执行此操作?这对我没用。【参考方案2】:FFmpeg(在本例中为 libavformat/codec)API 非常接近地映射 ffmpeg.exe 命令行参数。要打开文件,请使用avformat_open_input_file()。最后两个参数可以为 NULL。这将为您填写 AVFormatContext。现在您开始在循环中使用av_read_frame() 读取帧。 pkt.stream_index 会告诉你每个数据包属于哪个流,而avformatcontext->streams[pkt.stream_index] 是附带的流信息,它告诉你它使用什么编解码器,是否是视频/音频等。使用avformat_close()关闭。
对于多路复用,您使用逆向,有关详细信息,请参阅muxing。基本上它是allocate,avio_open2,add streams,用于输入文件中的每个现有流(基本上是 context->streams[]),avformat_write_header(),av_interleaved_write_frame() 在循环中,av_write_trailer() 关闭(和free最后分配的上下文)。
视频流的编码/解码是使用 libavcodec 完成的。对于从复用器获得的每个 AVPacket,请使用 avcodec_decode_video2()。使用 avcodec_encode_video2() 对输出 AVFrame 进行编码。请注意,两者都会引入延迟,因此对每个函数的前几次调用将不会返回任何数据,您需要通过调用每个函数以 NULL 输入数据来刷新缓存数据,以从中获取尾数据包/帧。 av_interleave_write_frame 将正确交错数据包,因此视频/音频流不会不同步(如:相同时间戳的视频数据包在 ts 文件中的音频数据包之后出现 MB)。
如果您需要有关 avcodec_decode_video2、avcodec_encode_video2、av_read_frame 或 av_interleaved_write_frame 的更详细示例,只需在 Google 上搜索“$function example”,您就会看到完整的示例,展示了如何正确使用它们。对于 x264 编码,在调用 avcodec_open2 进行编码质量设置时,AVCodecContext 中的set some default parameters。在 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);
[edit] 哦,我忘了一个部分,时间戳。每个 AVPacket 和 AVFrame 在其结构中都有一个 pts 变量,您可以使用它来决定是否在输出流中包含数据包/帧。因此,对于音频,您将使用解复用步骤中的AVPacket.pts 作为分隔符,对于视频,您将使用解码步骤中的AVFrame.pts 作为分隔符。它们各自的文档会告诉您它们属于哪个单位。
[edit2] 我看到您在没有实际代码的情况下仍然存在一些问题,所以这是一个真正的(工作的)转码器,它重新编码视频并重新混合音频。它可能有大量的错误、泄漏和缺乏适当的错误报告,它也不处理时间戳(我把它留给你作为练习),但它做了你要求的基本事情:
#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;
【讨论】:
我通过第一次尝试更新了我的答案。我遇到了一个我什至不知道如何开始调试的异常。 你有回溯吗?一些可能的答案是在av_init_packet(&outPacket)之后,你需要设置outPacket.data = NULL和outPacket.size = 0,或者你可能没有使用avcodec_open2()打开outputVideoStream.stream->codec变量?回溯会使问题更明显。 这是 EXC_BAD_ACCESS,所以我没有得到任何输出,应用程序立即停止。我应该如何使用 avcodec_open2 现在我正在使用 avcodec_copy_context 从输入流到输出流。 查看原始帖子中的编辑以获取测试+功能代码。 今晚我试试这个!以上是关于通过 ffmpegwrapper 切割 MPEG-TS 文件?的主要内容,如果未能解决你的问题,请参考以下文章
通过 HTTP (MPEG-DASH) 进行流式传输有啥意义?
如何在 Windows 7 上通过 DirectShow 播放 MPEG-TS 视频?