2013-07-23 2 views
6

ffmpeg 라이브러리를 사용하여 이미지에서 비디오를 만들려고합니다. 이미지의 크기는 1920x1080이고 .mkv 컨테이너를 사용하여 H.264로 인코딩해야합니다. 나는 여러 가지 문제에 직면 해있다. 나는 해결책에 더 가까워지고 있다고 생각하지만,이 문제에 관해서는 정말로 고심하고있다. 내가 사용하는 설정을 사용하면 내 비디오의 첫 번째 X 프레임 (비디오에 사용 된 이미지의 수와 수에 따라 약 40 개)이 인코딩되지 않습니다. avcodec_encode_video2는 got_picture_ptr = 0을 사용하여 어떤 오류도 반환하지 않습니다 (반환 값은 0 임). 결과는 실제로 예상대로 보이지만 1 초가 이상하게 불안정한 비디오입니다.ffmpeg libav 및 libx264를 사용하여 이미지에서 비디오 만들기?

그래서 이것은 내가 비디오 파일을 만드는 방법은 다음과 같습니다

// m_codecContext is an instance variable of type AVCodecContext * 
// m_formatCtx is an instance variable of type AVFormatContext * 

// outputFileName is a valid filename ending with .mkv 
AVOutputFormat *oformat = av_guess_format(NULL, outputFileName, NULL); 
if (oformat == NULL) 
{ 
    oformat = av_guess_format("mpeg", NULL, NULL); 
} 

// oformat->video_codec is AV_CODEC_ID_H264 
AVCodec *codec = avcodec_find_encoder(oformat->video_codec); 

m_codecContext = avcodec_alloc_context3(codec); 
m_codecContext->codec_id = oformat->video_codec; 
m_codecContext->codec_type = AVMEDIA_TYPE_VIDEO; 
m_codecContext->gop_size = 30; 
m_codecContext->bit_rate = width * height * 4 
m_codecContext->width = width; 
m_codecContext->height = height; 
m_codecContext->time_base = (AVRational){1,frameRate}; 
m_codecContext->max_b_frames = 1; 
m_codecContext->pix_fmt = AV_PIX_FMT_YUV420P; 

m_formatCtx = avformat_alloc_context(); 
m_formatCtx->oformat = oformat; 
m_formatCtx->video_codec_id = oformat->video_codec; 

snprintf(m_formatCtx->filename, sizeof(m_formatCtx->filename), "%s", outputFileName); 

AVStream *videoStream = avformat_new_stream(m_formatCtx, codec); 
if(!videoStream) 
{ 
    printf("Could not allocate stream\n"); 
} 
videoStream->codec = m_codecContext; 

if(m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER) 
{ 
    m_codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER; 
} 

avcodec_open2(m_codecContext, codec, NULL) < 0); 
avio_open(&m_formatCtx->pb, outputFileName.toStdString().c_str(), AVIO_FLAG_WRITE); 
avformat_write_header(m_formatCtx, NULL); 

이 프레임을 추가하는 방법입니다 :

void VideoCreator::writeImageToVideo(const QSharedPointer<QImage> &img, int frameIndex) 
{ 
    AVFrame *frame = avcodec_alloc_frame(); 

    /* alloc image and output buffer */ 

    int size = m_codecContext->width * m_codecContext->height; 
    int numBytes = avpicture_get_size(m_codecContext->pix_fmt, m_codecContext->width, m_codecContext->height); 

    uint8_t *outbuf = (uint8_t *)malloc(numBytes); 
    uint8_t *picture_buf = (uint8_t *)av_malloc(numBytes); 

    int ret = av_image_fill_arrays(frame->data, frame->linesize, picture_buf, m_codecContext->pix_fmt, m_codecContext->width, m_codecContext->height, 1); 

    frame->data[0] = picture_buf; 
    frame->data[1] = frame->data[0] + size; 
    frame->data[2] = frame->data[1] + size/4; 
    frame->linesize[0] = m_codecContext->width; 
    frame->linesize[1] = m_codecContext->width/2; 
    frame->linesize[2] = m_codecContext->width/2; 

    fflush(stdout); 


    for (int y = 0; y < m_codecContext->height; y++) 
    { 
     for (int x = 0; x < m_codecContext->width; x++) 
     { 
      unsigned char b = img->bits()[(y * m_codecContext->width + x) * 4 + 0]; 
      unsigned char g = img->bits()[(y * m_codecContext->width + x) * 4 + 1]; 
      unsigned char r = img->bits()[(y * m_codecContext->width + x) * 4 + 2]; 

      unsigned char Y = (0.257 * r) + (0.504 * g) + (0.098 * b) + 16; 

      frame->data[0][y * frame->linesize[0] + x] = Y; 

      if (y % 2 == 0 && x % 2 == 0) 
      { 
       unsigned char V = (0.439 * r) - (0.368 * g) - (0.071 * b) + 128; 
       unsigned char U = -(0.148 * r) - (0.291 * g) + (0.439 * b) + 128; 

       frame->data[1][y/2 * frame->linesize[1] + x/2] = U; 
       frame->data[2][y/2 * frame->linesize[2] + x/2] = V; 
      } 
     } 
    } 

    int pts = frameIndex;//(1.0/30.0) * 90.0 * frameIndex; 

    frame->pts = pts;//av_rescale_q(m_codecContext->coded_frame->pts, m_codecContext->time_base, formatCtx->streams[0]->time_base); //(1.0/30.0) * 90.0 * frameIndex; 

    int got_packet_ptr; 
    AVPacket packet; 
    av_init_packet(&packet); 
    packet.data = outbuf; 
    packet.size = numBytes; 
    packet.stream_index = formatCtx->streams[0]->index; 
    packet.flags |= AV_PKT_FLAG_KEY; 
    packet.pts = packet.dts = pts; 
    m_codecContext->coded_frame->pts = pts; 

    ret = avcodec_encode_video2(m_codecContext, &packet, frame, &got_packet_ptr); 
    if (got_packet_ptr != 0) 
    { 
     m_codecContext->coded_frame->pts = pts; // Set the time stamp 

     if (m_codecContext->coded_frame->pts != (0x8000000000000000LL)) 
     { 
      pts = av_rescale_q(m_codecContext->coded_frame->pts, m_codecContext->time_base, formatCtx->streams[0]->time_base); 
     } 
     packet.pts = pts; 
     if(m_codecContext->coded_frame->key_frame) 
     { 
      packet.flags |= AV_PKT_FLAG_KEY; 
     } 

     std::cout << "pts: " << packet.pts << ", dts: " << packet.dts << std::endl; 

     av_interleaved_write_frame(formatCtx, &packet); 
     av_free_packet(&packet); 
    } 

    free(picture_buf); 
    free(outbuf); 
    av_free(frame); 
    printf("\n"); 
} 

이이 정리입니다 :

int numBytes = avpicture_get_size(m_codecContext->pix_fmt, m_codecContext->width, m_codecContext->height); 
int got_packet_ptr = 1; 

int ret; 
//  for(; got_packet_ptr != 0; i++) 
while (got_packet_ptr) 
{ 
    uint8_t *outbuf = (uint8_t *)malloc(numBytes); 

    AVPacket packet; 
    av_init_packet(&packet); 
    packet.data = outbuf; 
    packet.size = numBytes; 

    ret = avcodec_encode_video2(m_codecContext, &packet, NULL, &got_packet_ptr); 
    if (got_packet_ptr) 
    { 
     av_interleaved_write_frame(m_formatCtx, &packet); 
    } 

    av_free_packet(&packet); 
    free(outbuf); 
} 

av_write_trailer(formatCtx); 

avcodec_close(m_codecContext); 
av_free(m_codecContext); 
printf("\n"); 

PTS 및 DTS 값에 묶여 있다고 가정하지만 E 아주 좋아. 프레임 색인이 가장 합리적인 것 같습니다. 이미지가 정확합니다. 문제없이 파일에 저장할 수 있습니다. 나는 아이디어가 부족하다.

이 어떤 도움 인 경우이 상기 출력입니다 : 나보다 더 잘 알고 거기에 누군가 ...

건배, marikaner

UPDATE이 있다면 나는 매우 감사 할 것 동영상 인코딩 끝 :

[libx264 @ 0x7fffc00028a0] frame I:19 Avg QP:14.24 size:312420 
[libx264 @ 0x7fffc00028a0] frame P:280 Avg QP:19.16 size:148867 
[libx264 @ 0x7fffc00028a0] frame B:181 Avg QP:21.31 size: 40540 
[libx264 @ 0x7fffc00028a0] consecutive B-frames: 24.6% 75.4% 
[libx264 @ 0x7fffc00028a0] mb I I16..4: 30.9% 45.5% 23.7% 
[libx264 @ 0x7fffc00028a0] mb P I16..4: 4.7% 9.1% 4.5% P16..4: 23.5% 16.6% 12.6% 0.0% 0.0% skip:28.9% 
[libx264 @ 0x7fffc00028a0] mb B I16..4: 0.6% 0.5% 0.3% B16..8: 26.7% 11.0% 5.5% direct: 3.9% skip:51.5% L0:39.4% L1:45.0% BI:15.6% 
[libx264 @ 0x7fffc00028a0] final ratefactor: 19.21 
[libx264 @ 0x7fffc00028a0] 8x8 transform intra:48.2% inter:47.3% 
[libx264 @ 0x7fffc00028a0] coded y,uvDC,uvAC intra: 54.9% 53.1% 30.4% inter: 25.4% 13.5% 4.2% 
[libx264 @ 0x7fffc00028a0] i16 v,h,dc,p: 41% 29% 11% 19% 
[libx264 @ 0x7fffc00028a0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 16% 26% 31% 3% 4% 3% 7% 3% 6% 
[libx264 @ 0x7fffc00028a0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 30% 26% 14% 4% 5% 4% 7% 4% 7% 
[libx264 @ 0x7fffc00028a0] i8c dc,h,v,p: 58% 26% 13% 3% 
[libx264 @ 0x7fffc00028a0] Weighted P-Frames: Y:17.1% UV:3.6% 
[libx264 @ 0x7fffc00028a0] ref P L0: 63.1% 21.4% 11.4% 4.1% 0.1%  
[libx264 @ 0x7fffc00028a0] ref B L0: 85.7% 14.3% 
[libx264 @ 0x7fffc00028a0] kb/s:27478.30 

답변

2

리바브가 초기 프레임 처리를 지연시킬 수 있습니다. 좋은 방법은 모든 프레임 처리를 마친 후에 지연된 프레임을 확인하는 것입니다.

int i=NUMBER_OF_FRAMES_PREVIOUSLY_ENCODED 
for(; got_packet_ptr; i++) 
    ret = avcodec_encode_video2(m_codecContext, &packet, NULL, &got_packet_ptr); 
//Write the packets to a container after this. 

점은 인코딩 할 프레임 대신에 NULL 포인터를 전달하고 당신이 얻을 패킷이 비어있을 때까지 계속 그렇게 할 것입니다 : 다음과 같이 수행됩니다. 코드 예는 "지연된 프레임 가져 오기"아래의 부분에 대해서는 this link을 참조하십시오.

쉬운 방법은 밖으로 B 프레임의 수는

m_codecContext->max_b_frames = 0; 

0이 잘 작동하는지 알려줘로 설정하는 것입니다.

또한 libx264 API를 전혀 사용하지 않았습니다. 인코딩 비디오에 libx264 API를 사용할 수 있으며 더 간단하고 명확한 구문을 사용합니다. 또한 설정 및 성능 향상에 대한 제어 기능이 향상되었습니다.

mkv 컨테이너에 비디오 스트림을 쓰려면 여전히 libav 라이브러리를 사용해야합니다. 그래도.

+0

시간을 내 주셔서 대단히 감사합니다. 불행히도 b 프레임의 수를 설정하거나 지연된 프레임을 쓰는 것도 그 트릭을 수행하는 것 같지 않습니다. 프로그램이 루프에 들어가기 때문에 분명히 지연된 프레임이 있지만. 비디오는 실제로 덜 불안해 보이지만 여전히 정확하지는 않습니다. 2 초 후에 2 개의 정지 영상이 2 초 동안 표시되는 구멍이있는 것처럼 보입니다. – marikaner

+0

인코딩 할 비디오의 총 수와 인코딩하려는 총 이미지 수를 지정할 수 있습니까? av_interleaved_write()에 대한 호출 수를 확인할 수 있습니다 (업데이트 당 480이어야 함). 또한 frameIndex 계산이란 무엇입니까? –

+0

예, 인코딩 할 들여 쓰기 할 이미지의 수는 480입니다. frameIndex는 각 프레임에서 0에서 479까지 증가하는 정수입니다. av_interleaved_write()는 실제 프레임을 사용하는 avcodec_encode_video2 이후 442 회, NULL을 사용하여 avcodec_encode_video2 이후 38 회 호출됩니다. – marikaner