0

I'm trying to write a function to cut videos/audios FFmpeg C APIs in C++. I started with the remuxing.c example from FFmpeg GitHub repository, and tried to apply the same changes mentioned in this question, but I'm getting blank screen in the beginning of the output equal to the duration that I want to cut. This is the function I came with (Differences between the function and the remuxing example noted with // <- HERE):

int cut_video(const char *in_filename, const char *out_filename, double from_seconds, double end_seconds) {
  const AVOutputFormat *ofmt = NULL;
  AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
  AVPacket *pkt = NULL;
  int ret, i;
  int stream_index = 0;
  int *stream_mapping = NULL;
  int stream_mapping_size = 0;

  pkt = av_packet_alloc();
  if (!pkt) {
    fprintf(stderr, "Could not allocate AVPacket\n");
    return 1;
  }

  if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
    fprintf(stderr, "Could not open input file '%s'", in_filename);
    goto end;
  }

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

  av_dump_format(ifmt_ctx, 0, in_filename, 0);

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

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

  stream_mapping_size = ifmt_ctx->nb_streams;
  stream_mapping = (int *)av_calloc(stream_mapping_size, sizeof(*stream_mapping));
  if (!stream_mapping) {
    ret = AVERROR(ENOMEM);
    goto end;
  }

  ofmt = ofmt_ctx->oformat;

  for (i = 0; i < ifmt_ctx->nb_streams; i++) {
    AVStream *out_stream;
    AVStream *in_stream = ifmt_ctx->streams[i];
    AVCodecParameters *in_codecpar = in_stream->codecpar;

    if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
        in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
      stream_mapping[i] = -1;
      continue;
    }

    stream_mapping[i] = stream_index++;

    out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
      fprintf(stderr, "Failed allocating output stream\n");
      ret = AVERROR_UNKNOWN;
      goto end;
    }

    ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
    if (ret < 0) {
      fprintf(stderr, "Failed to copy codec parameters\n");
      goto end;
    }
    out_stream->codecpar->codec_tag = 0;
  }
  av_dump_format(ofmt_ctx, 0, out_filename, 1);

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

  ret = avformat_write_header(ofmt_ctx, NULL);
  if (ret < 0) {
    fprintf(stderr, "Error occurred when opening output file\n");
    goto end;
  }

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

    ret = av_read_frame(ifmt_ctx, pkt);
    if (ret < 0) break;

    in_stream = ifmt_ctx->streams[pkt->stream_index];
    if (pkt->stream_index >= stream_mapping_size || stream_mapping[pkt->stream_index] < 0 ||
        av_q2d(in_stream->time_base) * pkt->pts > end_seconds) {  // <- HERE
      av_packet_unref(pkt);
      continue;
    }

    pkt->stream_index = stream_mapping[pkt->stream_index];
    out_stream = ofmt_ctx->streams[pkt->stream_index];
    log_packet(ifmt_ctx, pkt, "in");

    /* copy packet */
    av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base);
    pkt->pos = -1;
    log_packet(ofmt_ctx, pkt, "out");

    ret = av_interleaved_write_frame(ofmt_ctx, pkt);
    /* pkt is now blank (av_interleaved_write_frame() takes ownership of
     * its contents and resets pkt), so that no unreferencing is necessary.
     * This would be different if one used av_write_frame(). */
    if (ret < 0) {
      fprintf(stderr, "Error muxing packet\n");
      break;
    }
  }

  av_write_trailer(ofmt_ctx);
end:
  av_packet_free(&pkt);

  avformat_close_input(&ifmt_ctx);

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

  av_freep(&stream_mapping);

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

  return 0;
}

And here is how I call it:

cut_video("/Users/aliosm/Desktop/1.mp4", "/Users/aliosm/Desktop/2.mp4", 10, 40);

I searched a lot on Google and I didn't find anything useful related to this specific use-case, do you have any idea?

AliOsm
  • 541
  • 2
  • 6
  • 20

1 Answers1

0

Finally, I was able to do that by the help from #ffmpeg channel community on Libera.Chat IRC. The final code:

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
}

/**
 * @brief Print the information of the passed packet.
 *
 * @fn logPacket
 * @param avFormatContext AVFormatContext of the given packet.
 * @param avPacket AVPacket to log.
 * @param tag String to tag the log output.
 */
void logPacket(const AVFormatContext *avFormatContext, const AVPacket *avPacket, const QString tag) {
  AVRational *timeBase = &avFormatContext->streams[avPacket->stream_index]->time_base;

  qDebug() << QString("%1: pts:%2 pts_time:%3 dts:%4 dts_time:%5 duration:%6 duration_time:%7 stream_index:%8")
                  .arg(tag)
                  .arg(av_ts2str(avPacket->pts))
                  .arg(av_ts2timestr(avPacket->pts, timeBase))
                  .arg(av_ts2str(avPacket->dts))
                  .arg(av_ts2timestr(avPacket->dts, timeBase))
                  .arg(av_ts2str(avPacket->duration))
                  .arg(av_ts2timestr(avPacket->duration, timeBase))
                  .arg(avPacket->stream_index);
}

/**
 * @brief Cut a file in the given input file path based on the start and end seconds, and output the cutted file to the
 * given output file path.
 *
 * @fn cutFile
 * @param inputFilePath Input file path to be cutted.
 * @param startSeconds Cutting start time in seconds.
 * @param endSeconds Cutting end time in seconds.
 * @param outputFilePath Output file path to write the new cutted file.
 *
 * @details This function will take an input file path and cut it based on the given start and end seconds. The cutted
 * file will then be outputted to the given output file path.
 *
 * @return True if the cutting operation finished successfully, false otherwise.
 */
bool cutFile(const QString& inputFilePath, const long long& startSeconds, const long long& endSeconds,
                            const QString& outputFilePath) {
  int operationResult;

  AVPacket* avPacket = NULL;
  AVFormatContext* avInputFormatContext = NULL;
  AVFormatContext* avOutputFormatContext = NULL;

  avPacket = av_packet_alloc();
  if (!avPacket) {
    qCritical("Failed to allocate AVPacket.");
    return false;
  }

  try {
    operationResult = avformat_open_input(&avInputFormatContext, inputFilePath.toStdString().c_str(), 0, 0);
    if (operationResult < 0) {
      throw std::runtime_error(QString("Failed to open the input file '%1'.").arg(inputFilePath).toStdString().c_str());
    }

    operationResult = avformat_find_stream_info(avInputFormatContext, 0);
    if (operationResult < 0) {
      throw std::runtime_error(QString("Failed to retrieve the input stream information.").toStdString().c_str());
    }

    avformat_alloc_output_context2(&avOutputFormatContext, NULL, NULL, outputFilePath.toStdString().c_str());
    if (!avOutputFormatContext) {
      operationResult = AVERROR_UNKNOWN;
      throw std::runtime_error(QString("Failed to create the output context.").toStdString().c_str());
    }

    int streamIndex = 0;
    int streamMapping[avInputFormatContext->nb_streams];
    int streamRescaledStartSeconds[avInputFormatContext->nb_streams];
    int streamRescaledEndSeconds[avInputFormatContext->nb_streams];

    // Copy streams from the input file to the output file.
    for (int i = 0; i < avInputFormatContext->nb_streams; i++) {
      AVStream* outStream;
      AVStream* inStream = avInputFormatContext->streams[i];

      streamRescaledStartSeconds[i] = av_rescale_q(startSeconds * AV_TIME_BASE, AV_TIME_BASE_Q, inStream->time_base);
      streamRescaledEndSeconds[i] = av_rescale_q(endSeconds * AV_TIME_BASE, AV_TIME_BASE_Q, inStream->time_base);

      if (inStream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
          inStream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
          inStream->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
        streamMapping[i] = -1;
        continue;
      }

      streamMapping[i] = streamIndex++;

      outStream = avformat_new_stream(avOutputFormatContext, NULL);
      if (!outStream) {
        operationResult = AVERROR_UNKNOWN;
        throw std::runtime_error(QString("Failed to allocate the output stream.").toStdString().c_str());
      }

      operationResult = avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);
      if (operationResult < 0) {
        throw std::runtime_error(
            QString("Failed to copy codec parameters from input stream to output stream.").toStdString().c_str());
      }
      outStream->codecpar->codec_tag = 0;
    }

    if (!(avOutputFormatContext->oformat->flags & AVFMT_NOFILE)) {
      operationResult = avio_open(&avOutputFormatContext->pb, outputFilePath.toStdString().c_str(), AVIO_FLAG_WRITE);
      if (operationResult < 0) {
        throw std::runtime_error(
            QString("Failed to open the output file '%1'.").arg(outputFilePath).toStdString().c_str());
      }
    }

    operationResult = avformat_write_header(avOutputFormatContext, NULL);
    if (operationResult < 0) {
      throw std::runtime_error(QString("Error occurred when opening output file.").toStdString().c_str());
    }

    operationResult = avformat_seek_file(avInputFormatContext, -1, INT64_MIN, startSeconds * AV_TIME_BASE,
                                         startSeconds * AV_TIME_BASE, 0);
    if (operationResult < 0) {
      throw std::runtime_error(
          QString("Failed to seek the input file to the targeted start position.").toStdString().c_str());
    }

    while (true) {
      operationResult = av_read_frame(avInputFormatContext, avPacket);
      if (operationResult < 0) break;

      // Skip packets from unknown streams and packets after the end cut position.
      if (avPacket->stream_index >= avInputFormatContext->nb_streams || streamMapping[avPacket->stream_index] < 0 ||
          avPacket->pts > streamRescaledEndSeconds[avPacket->stream_index]) {
        av_packet_unref(avPacket);
        continue;
      }

      avPacket->stream_index = streamMapping[avPacket->stream_index];
      logPacket(avInputFormatContext, avPacket, "in");

      // Shift the packet to its new position by subtracting the rescaled start seconds.
      avPacket->pts -= streamRescaledStartSeconds[avPacket->stream_index];
      avPacket->dts -= streamRescaledStartSeconds[avPacket->stream_index];

      av_packet_rescale_ts(avPacket, avInputFormatContext->streams[avPacket->stream_index]->time_base,
                           avOutputFormatContext->streams[avPacket->stream_index]->time_base);
      avPacket->pos = -1;
      logPacket(avOutputFormatContext, avPacket, "out");

      operationResult = av_interleaved_write_frame(avOutputFormatContext, avPacket);
      if (operationResult < 0) {
        throw std::runtime_error(QString("Failed to mux the packet.").toStdString().c_str());
      }
    }

    av_write_trailer(avOutputFormatContext);
  } catch (std::runtime_error e) {
    qCritical("%s", e.what());
  }

  av_packet_free(&avPacket);

  avformat_close_input(&avInputFormatContext);

  if (avOutputFormatContext && !(avOutputFormatContext->oformat->flags & AVFMT_NOFILE))
    avio_closep(&avOutputFormatContext->pb);
  avformat_free_context(avOutputFormatContext);

  if (operationResult < 0 && operationResult != AVERROR_EOF) {
    qCritical("%s", QString("Error occurred: %1.").arg(av_err2str(operationResult)).toStdString().c_str());
    return false;
  }

  return true;
}

The code is written in C++, and it is using some Qt related classes, you can remove them and use the code on plain C++ projects.

I tried my best to make it readable, I hope it is good and helpful.


  • Update 1: I updated the code to fix a bug in it.
  • Update 2: I updated the code to do some refactoring.
AliOsm
  • 541
  • 2
  • 6
  • 20