使用ffmpeg进行编码时丢失质量

时间:2016-05-20 12:57:05

标签: c++ ffmpeg video-processing

我正在使用ffmpeg的c库来读取视频中的帧并创建一个应该与输入相同的输出文件。 然而,在这个过程中的某个地方,一些质量会丢失,结果“不那么尖锐”。我的猜测是问题是编码和帧太压缩(也因为文件的大小显着减小)。 编码器中是否有一些参数允许我控制结果的质量?我发现AVCodecContext有一个compression_level成员,但更改它似乎没有任何影响。

我在这里发布部分代码,以防它可以提供帮助。我会说当我设置编解码器时,必须在OutputVideoBuilder的init函数中更改某些内容。传递给该方法的AVCodecContext与InputVideoHandler相同。 以下是我创建的用于包装ffmpeg功能的两个主要类:

// This class opens the video files and sets the decoder
class InputVideoHandler {
public:
  InputVideoHandler(char* name);
  ~InputVideoHandler();
  AVCodecContext* getCodecContext();
  bool readFrame(AVFrame* frame, int* success);

private:
  InputVideoHandler();
  void init(char* name);
  AVFormatContext* formatCtx;
  AVCodec* codec;
  AVCodecContext* codecCtx;
  AVPacket packet;
  int streamIndex;
};

void InputVideoHandler::init(char* name) {
  streamIndex = -1;
  int numStreams;

  if (avformat_open_input(&formatCtx, name, NULL, NULL) != 0)
    throw std::exception("Invalid input file name.");

  if (avformat_find_stream_info(formatCtx, NULL)<0)
    throw std::exception("Could not find stream information.");

  numStreams = formatCtx->nb_streams;

  if (numStreams < 0)
    throw std::exception("No streams in input video file.");

  for (int i = 0; i < numStreams; i++) {
    if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
      streamIndex = i;
      break;
    }
  }

  if (streamIndex < 0)
    throw std::exception("No video stream in input video file.");

  // find decoder using id
  codec = avcodec_find_decoder(formatCtx->streams[streamIndex]->codec->codec_id);
  if (codec == nullptr)
    throw std::exception("Could not find suitable decoder for input file.");

  // copy context from input stream
  codecCtx = avcodec_alloc_context3(codec);
  if (avcodec_copy_context(codecCtx, formatCtx->streams[streamIndex]->codec) != 0)
    throw std::exception("Could not copy codec context from input stream.");

  if (avcodec_open2(codecCtx, codec, NULL) < 0)
    throw std::exception("Could not open decoder.");
}

// frame must be initialized with av_frame_alloc() before!
// Returns true if there are other frames, false if not.
// success == 1 if frame is valid, 0 if not.
bool InputVideoHandler::readFrame(AVFrame* frame, int* success) {
  *success = 0;
  if (av_read_frame(formatCtx, &packet) < 0)
    return false;
  if (packet.stream_index == streamIndex) {
    avcodec_decode_video2(codecCtx, frame, success, &packet);
  }
  av_free_packet(&packet);
  return true;
}

// This class opens the output and write frames to it
class OutputVideoBuilder{
public:
  OutputVideoBuilder(char* name, AVCodecContext* inputCtx);
  ~OutputVideoBuilder();
  void writeFrame(AVFrame* frame);
  void writeVideo();

private:
  OutputVideoBuilder();
  void init(char* name, AVCodecContext* inputCtx);
  void logMsg(AVPacket* packet, AVRational* tb);
  AVFormatContext* formatCtx;
  AVCodec* codec;
  AVCodecContext* codecCtx;
  AVStream* stream;
};

void OutputVideoBuilder::init(char* name, AVCodecContext* inputCtx) {
  if (avformat_alloc_output_context2(&formatCtx, NULL, NULL, name) < 0)
    throw std::exception("Could not determine file extension from provided name.");

  codec = avcodec_find_encoder(inputCtx->codec_id);
  if (codec == nullptr) {
    throw std::exception("Could not find suitable encoder.");
  }

  codecCtx = avcodec_alloc_context3(codec);
  if (avcodec_copy_context(codecCtx, inputCtx) < 0)
    throw std::exception("Could not copy output codec context from input");

  codecCtx->time_base = inputCtx->time_base;
  codecCtx->compression_level = 0;

  if (avcodec_open2(codecCtx, codec, NULL) < 0)
    throw std::exception("Could not open encoder.");

  stream = avformat_new_stream(formatCtx, codec);
  if (stream == nullptr) {
    throw std::exception("Could not allocate stream.");
  }

  stream->id = formatCtx->nb_streams - 1;
  stream->codec = codecCtx;
  stream->time_base = codecCtx->time_base;



  av_dump_format(formatCtx, 0, name, 1);
  if (!(formatCtx->oformat->flags & AVFMT_NOFILE)) {
    if (avio_open(&formatCtx->pb, name, AVIO_FLAG_WRITE) < 0) {
      throw std::exception("Could not open output file.");
    }
  }

  if (avformat_write_header(formatCtx, NULL) < 0) {
    throw std::exception("Error occurred when opening output file.");
  }

}

void OutputVideoBuilder::writeFrame(AVFrame* frame) {
  AVPacket packet = { 0 };
  int success;
  av_init_packet(&packet);

  if (avcodec_encode_video2(codecCtx, &packet, frame, &success))
    throw std::exception("Error encoding frames");

  if (success) {
    av_packet_rescale_ts(&packet, codecCtx->time_base, stream->time_base);
    packet.stream_index = stream->index;
    logMsg(&packet,&stream->time_base);
    av_interleaved_write_frame(formatCtx, &packet);
  }
  av_free_packet(&packet);
}

这是读取和写入帧的主要功能的一部分:

 while (inputHandler->readFrame(frame,&gotFrame)) {

    if (gotFrame) {
      try {
        outputBuilder->writeFrame(frame);
      }
      catch (std::exception e) {
        std::cout << e.what() << std::endl;
        return -1;
      }
    }
  }

2 个答案:

答案 0 :(得分:1)

你的qmin / qmax答案是部分正确的,但它忽略了这一点,因为质量确实上升了,但压缩比(就每位质量而言)将受到严重影响,因为你限制了qmin / qmax范围 - 也就是说,如果最佳地使用编码器,你将花费更多的比特来达到相同的质量。

要在不影响压缩比的情况下提高质量,您需要实际提高质量目标。如何执行此操作会因编解码器而略有不同,但通常会增加目标CRF值或目标比特率。有关命令行选项,请参阅例如H264个文档。 HEVC / VP9也有相同的文档。要在C API中使用这些选项,请使用具有相同选项名称/值的av_opt_set()

答案 1 :(得分:0)

如果这对其他人有用,我会添加damjeux建议的答案,这对我有用。 AVCodecContex有两个成员qmin和qmax,它们控制编码器的QP(量化参数)。默认情况下,在我的情况下,qmin是2,qmax是31.通过将qmax设置为较低的值,输出的质量会提高。