2
votes

I am using the FFMPEG libraries to trim video files. I do this all as a remux, with no encoding or decoding.

Trimming currently works correctly with audio, but the trimmed video data appears as a solid color, with small squares of pixels changing. I believe this is because I am not catching/writing a keyframe. It is my understanding that av_seek_frame will seek to a keyframe, which does not seem to be the case..

If need be, can I decode and then reencode just the first video frame I read after seeking? This will probably be more code than reencoding every frame, but speed is the primary issue here, not complexity.

Thank you for any help. Also I apologize if I am misunderstanding something to do with video files, I'm still new to this.

Example output frame: enter image description here

Code, adapted from the remux example provided with ffmpeg:

const char *out_filename = "aaa.mp4";

FILE *fp = fdopen(fd, "r");
fseek(fp, 0, SEEK_SET);

if ( fp ) {

    // Build an ffmpeg file
    char path[512];
    sprintf(path, "pipe:%d", fileno(fp));

    // Turn on verbosity
    av_log_set_level( AV_LOG_DEBUG );
    av_log_set_callback( avLogCallback );


    av_register_all();
    avcodec_register_all();


    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = avformat_alloc_context(), *ofmt_ctx = NULL;
    AVPacket pkt;
    int ret, i;


    if ((ret = avformat_open_input(&ifmt_ctx, path, av_find_input_format("mp4"), NULL)) < 0) {
        LOG("Could not open input file '%s'", path);
        goto end;
    }

    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        LOG("Failed to retrieve input stream information", "");
        goto end;
    }


    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        LOG("Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    ofmt = ofmt_ctx->oformat;


    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);

        if (!out_stream) {
            LOG("Failed allocating output stream\n");
            goto end;
        }

        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        if (ret < 0) {
            LOG("Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        out_stream->codecpar->codec_tag = 0;
    }

    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            LOG("Could not open output file '%s'", out_filename);
            goto end;
        }
    }

    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        LOG("Error occurred when writing headers\n");
        goto end;
    }

    ret = av_seek_frame(ifmt_ctx, -1, from_seconds * AV_TIME_BASE, AVSEEK_FLAG_ANY);
    if (ret < 0) {
        LOG("Error seek\n");
        goto end;
    }

    int64_t *dts_start_from;
    int64_t *pts_start_from;
    dts_start_from = (int64_t *) malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
    memset(dts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
    pts_start_from = (int64_t *) malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
    memset(pts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);

    while (1) {
        AVStream *in_stream, *out_stream;

        ret = av_read_frame(ifmt_ctx, &pkt);
        LOG("while %d", ret);
        LOG("Packet size: %d", pkt.size);
        LOG("Packet stream: %d", pkt.stream_index);
        if (ret < 0)
            break;

        in_stream = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];

        if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
            av_packet_unref(&pkt);
            break;
        }

        if (dts_start_from[pkt.stream_index] == 0) {
            dts_start_from[pkt.stream_index] = pkt.dts;
            printf("dts_start_from: %s\n", av_ts_make_string((char[AV_TS_MAX_STRING_SIZE]){0},dts_start_from[pkt.stream_index]));
        }
        if (pts_start_from[pkt.stream_index] == 0) {
            pts_start_from[pkt.stream_index] = pkt.pts;
            printf("pts_start_from: %s\n", av_ts_make_string((char[AV_TS_MAX_STRING_SIZE]){0},pts_start_from[pkt.stream_index]));
        }

        /* copy packet */
        pkt.pts = ::av_rescale_q_rnd(pkt.pts - pts_start_from[pkt.stream_index], 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 - dts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, (AVRounding) (AV_ROUND_NEAR_INF |
                                                                                                                                            AV_ROUND_PASS_MINMAX));
        if (pkt.pts < 0) {
            pkt.pts = 0;
        }
        if (pkt.dts < 0) {
            pkt.dts = 0;
        }
        pkt.duration = (int) av_rescale_q((int64_t) pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        printf("\n");

        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            LOG("Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);
    }
    free(dts_start_from);
    free(pts_start_from);

    av_write_trailer(ofmt_ctx);

    end:
    LOG("END");

    avformat_close_input(&ifmt_ctx);

    // Close output
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);

    if (ret < 0 && ret != AVERROR_EOF) {
        LOG("-- Error occurred: %s\n", av_err2str(ret));
        return 1;
    }
}
1

1 Answers

2
votes

For frame-granularity remuxing (not just starting at a keyframe), at the very least you'll need to encode a keyframe to replace the non-key frame you started at, but if the stream uses B frames (common for mp4 with h264/etc.) then it may not suffice. The safest approach is to reencode everything from the frame you want to start with up until (but not including) the next keyframe, then simply remux-copy everything starting with the existing keyframe there.