Подтвердить что ты не робот

Вырезать файл MPEG-TS через ffmpegwrapper?

У меня есть файлы MPEG-TS на устройстве. Я хотел бы сократить довольно точное время с момента запуска файлов на устройстве.

Используя FFmpegWrapper в качестве базы, я надеюсь достичь этого.

Я немного потерял API C API ffmpeg. С чего начать?

Я попытался просто отбросить все пакеты до начала 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

Кажется, это то, что мне нужно. Я думаю, что требуется повторное кодирование, чтобы видео могло начинаться с любой произвольной точки, а не с существующего ключевого кадра. Если есть более эффективное решение, это здорово. Если нет, это достаточно хорошо.

EDIT: Здесь моя попытка. Я собираю различные элементы, которые я не совсем понимаю, скопировал из здесь. Я сейчас ухожу от "разрезающей" части, чтобы попытаться получить аудио + видео, закодированные без сложностей слоев. Я получаю EXC_BAD_ACCESS на avcodec_encode_video2(...)

- (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];
    });
}
4b9b3361

Ответ 1

API 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 для получения дополнительной информации. В основном это выделить, avio_open2, добавить потоки для каждого существующего потока во входном файле (в основном context- > streams []), avformat_write_header(), av_interleaved_write_frame() в цикле av_write_trailer(), чтобы закрыть (и бесплатно выделенный контекст в конце).

Кодирование/декодирование видеопотока выполняется с помощью libavcodec. Для каждого AVPacket, который вы получаете из мультиплеера, используйте avcodec_decode_video2(). Используйте avcodec_encode_video2() для кодирования выходного AVFrame. Обратите внимание, что оба будут вводить задержку, поэтому первые несколько вызовов для каждой функции не будут возвращать какие-либо данные, и вам необходимо очистить кэшированные данные, вызвав каждую функцию с входными данными NULL, чтобы получить из нее хвостовые пакеты/фреймы. av_interleave_write_frame будет правильно чередовать пакеты, чтобы потоки видео/аудио не десинхронизировались (например, в видеопакетах той же метки времени появляются MBs после аудиопакетов в файле ts).

Если вам нужны более подробные примеры для avcodec_decode_video2, avcodec_encode_video2, av_read_frame или av_interleaved_write_frame, просто Google "пример функции $", и вы увидите полнофункциональные примеры, показывающие, как их правильно использовать. Для кодировки x264 установите некоторые параметры по умолчанию в AVCodecContext при вызове avcodec_open2 для настроек качества кодирования. В 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);

[править] О, я забыл одну часть, timestamping. Каждый AVPacket и AVFrame имеет переменную pts в своей структуре, и вы можете использовать это, чтобы решить, включать ли пакет/кадр в выходной поток. Итак, для аудио вы используете AVPacket.pts с шага demuxing как разделитель, а для видео вы будете использовать 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;
}

Ответ 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

Для справки, принятый ответ на этот вопрос:

Как мне разделить и объединить файлы с помощью ffmpeg, сохраняя все звуковые дорожки?

Как вы обнаружили, копия битового потока будет выбирать только один (аудио) трек, согласно документация по спецификации потока:

По умолчанию 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.

Я бы также объединил -vcodec copy -acodec copy из вашей исходной команды в -c copy, как указано выше, для компактности выражения.

Split:

Итак, объединяя те, которые вы хотите достичь в двух файлах с точки зрения разделения для последующего повторного соединения:

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 поддерживает конкатенацию файлов без повторного кодирования, подробно описывается в документации по конкатенации.

Создайте список подключаемых файлов (например, 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. Я оставлю это для вас, чтобы поэкспериментировать.