2016-05-20 48 views
1

我正在使用ffmpeg的c库从视频中读取帧,并创建应该与输入相同的输出文件。 但是,在这个过程中某个地方会有一些质量下降,结果是“不那么尖锐”。我的猜测是,问题在于编码,而且帧压缩得太紧(也因为文件的大小明显下降)。 编码器中是否有一些参数可以让我控制结果的质量?我发现AVCodecContext有一个compression_level成员,但改变它似乎没有任何作用。使用ffmpeg编码时丢失质量

我在这里发布我的部分代码以防万一。我会说,当我设置编解码器时,必须在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; 
     } 
    } 
    } 
+1

通常,量化参数(QP)允许您控制输出的质量。 – damjeux

+0

谢谢!降低编解码器上下文的成员qmax就足够了。也许你可以写评论作为答案。 – lupod

回答

1

您的qmin/qmax答案是部分正确的,但它忽略了这一点,因为质量的确上升了,但是当您限制qmin/qmax范围时,压缩比(按照每比特质量) - 也就是说,如果您最佳使用编码器,您将花费更多的位来完成与应有的相同的质量。

要提高质量而不损害压缩比,则需要实际提高质量目标。你如何做到这一点取决于编解码器,但通常会增加目标CRF值或目标比特率。有关命令行选项,请参阅H264文档。还有HEVC/VP9也有相同的文档。要在C API中使用这些选项,请使用具有相同选项名称/值的av_opt_set()

+0

谢谢你的解释! – lupod

0

在情况下,这可能是有用的给别人,我添加了damjeux建议的答案,这对我有用。 AVCodecContex有两个成员qmin和qmax来控制编码器的QP(量化参数)。默认情况下,在我的情况下,qmin是2,qmax是31.通过将qmax设置为较低的值,输出质量得到改善。