FFmpeg and SDL Tutorial - Synching Video FFmpeg


Tutorial 05: Synching Video


주의

이 튜토리얼을 만들때 싱킹코드는 ffplay.c에서 얻었으나. 현제 완전히 다른 프로그램이되었고 FFmpeg라이브러리가 향상되어 몇몇 전략이 변경되었다. 이 코드가 여전히 작동하지만 좋아보이지 않을 수 있다. 이 튜토리얼에서 사용하는 것보다 더 나은 방법이 있을 수 있다.

어떻게 비디오가 싱크되는가

지금까지 우리는 사용불가능한 영화 플레이어를 만들었다. 비디오를 재생하고 오디오를 재생하지만 이 것을 영화라고 부르기는 어렵다. 이제 무엇을 해야하는가?

PTS와 DTS

다행히, 오디오와 비디오 스트림은 얼마나 빠르게, 그리고 언제 재생해야 하는지에 대한 정보를 갖는다. 오디오 스트림은 샘플 레이트를 가지고 비디오 스트림은 초당 프레임수 값을 가진다. 그러나, 만약 프레임 갯수를 세는 정도로 그리고 프레임 레이트를 곱하는 정도로 싱크하면 오디오와의 싱크가 벗어날 수 있다. 대신에 스트림에서의 패킷을 디코딩 타임 스탬프(DTS)그리고 프레젠테이션 타임 스탬프(PTS) 값을 가진다. 이들 두 값을 이해하기위해서는 영화가 어떻게 저장되는지를 이해해야 한다. MPEG과 같은 몇몇 포맷에서 B프레임을 사용한다 (B는 bidirectional) 프레임의 다른 두가지는 I와 P이다. (I는 Intra, P는 Predicted) I프레임은 전체 이미지를 가진다. P프레임은 이전 I와 P프레임에 기반해 다른 것이나 델타를 갖는다. B프레임은 P프레임과 같지만 프레임 내에서 발결할 수 있는 정보에 기반해 이전에 또는 이후에 보여질 것인지가 결정된다! 이 설명은 어째서 avcodec_decode_video2 를 호출한 후 프레임이 완성되지 않는지를 설명한다.

자 영화를 가지고 있고 프레임이 I B B P와 같은 형태로 표시된다고 하자. 이제 P내의 정보를 B프레임을 보여주기 전에 알아야 한다. 이 때문에 프레임은 I P B B와 같은 형태로 저장되었을 수 있다. 이 것이 디코딩 타임 스탬프와 프레젠테이션 타임스탬프가 각 프레임에 저장된 이유이다. 디코딩 타임 스탬프는 디코딩을 해야할 때를 나타내고, 프레젠테이션 타임 스탬프는 언제 표시해야 할지를 나타낸다. 그러므로 이 경우에, 우리의 스트림은 다음과 같다.

   PTS: 1 4 2 3
   DTS: 1 2 3 4
Stream: I P B B

일반적으로 PTS와 DTS가 다른 것은 재생하는 스트림에 B프레임이 있을 때 이다.

av_read_frame() 을 통해 패킷을 얻어올 때, 패킷 내의 정보를 위한 PTS와 DTS를 갖는다. 하지만 새롭게 디코드된 로프레임의 PTS가 우리가 실제 원하는 것인데 이를 디스플레이할 때를 알아야 하기 때문이다.

운좋게도, FFmpeg은 "최고의 에포트" 타임스탬프를 제공하는데 av_frame_get_best_effort_timestamp() 이다.

싱킹

이제 프레임을 언제 표시해야 하는지 알지만 어떻게 할 수 있는가? 여기에 아이디어가 있다. 프레임을 보여준 후 다음 프레임이 언제 보여져야할지 확인한다. 얼마간의 시간 이후에 비디오를 다시 새로고침할지 새로운 타임아웃을 설정한다. 시스템 클락에 대한 다음 프레임의 PTS값을 확인하여 타임아웃이 얼마나 되어야 할 지 나타낸다. 이 접근은 작동하지만 살펴야할 두가지 이슈가 있다.

첫 번째는 다음 PTS가 언제 알 수 있게되는지에 대한 것이다. 이제, 현 PTS에 비디오 레이트만 더하면 될 거같다는 생각을 할텐데, 거의 맞다. 그러나, 프레임을 위한 몇몇 비디오 콜은 반복된다. 이 의미는 현 프레임을 특정 횟수만큼 반복해야 함을 의미한다. 이는 다음 프레임이 너무빨리 보여지게 될 수 있음을 의미한다. 그러므로 이를 위한 처리를 해야 한다.

두 번째 이슈는 비디오와 오디오가 싱크되지 않고 쫓아다니는 것이다. 만약 모든 것이 완벽하게 작동하면 걱정할 필요가 없다. 하지만 컴퓨터는 완벽하지 않고 모든 비디오 파일이 그렇지도 않다. 그러므로 세 가지의 선택을 가진다.: 비디오에 오디오 싱크, 오디오에 비디오 싱크, 그리고 외부 클락 (컴퓨터와 같은)에 싱크를 하는 것이다. 지금은 오디오에 비디오를 싱크한다.

코딩하기: 프레임 PTS얻기

큰 구조체에 추가적인 멤버를 추가해야한다. 먼저 비디오 스레드를보자. 디코드 스레드에의해 큐에 채워진 패킷을 꺼내는 곳임을 기억하자. 코드에서 이 부분에서 해야할 것은 주어진 프레임의 PTS를 avcodec_decode_video2 를 통해 얻는 것이다. 이에 대해 이야기했던 첫 번째 방법은 꽤 쉽다.

double pts;

for (;;) {
if (packet_queue_get(&is->videoq, packet, 1) < 0) {
// 패킷 얻기를 중단해야 함을 의미
break;
}
pts = 0;
// 비디오 프레임 디코드
len1 = avcodec_decode_video2(is->video_st->codec, pFrame, &frameFinished, packet);
if (packet->dts != AV_NOPTS_VALUE) {
pts = av_frame_get_best_effort_timestamp(pFrame);
} else {
pts = 0;
}
pts *= av_q2d(is->video_st->time_base);

무엇인지를 모를 때는 pts는 0으로 설정한다.

기술 노트: PTS에 int64를 사용했다. 이는 PTS가 숫자로 저장되어 있기 때문이다. 이 값은 타임스탬프로서 스트림의 time_base단위내의 시간에 맞춰 측정된 값이다. 예를 들어, 만약 스트림이 초당 24 프레임이라면 42의 PTS는 42번째 프레임으로서 초의 모든 1/24프레임에 등장한다. (확실하지 않을 수도 있다)

이 값을 프레임레이트로 나눠 초값으로 컨버트한다. 스트림의 time_base값은 1/framerate이므로 초에서의 PTS를 얻으려면 time_base로 나누면 된다.

코딩: PTS를 사용해 싱킹

이제 PTS를 설정했다. 위에서 언급한 두가지 동기화 문제를 다룰 때이다. synchronize_video 라 불리는 함수를 정의하며 PTS를 업데이트하여 모든 것이 싱크되게 할 것이다. 이 함수는 프레임에대해 PTS값을 가져오지 못하는 경우에 대해서도 다룬다. 동시에 다음프레임이 예측될 때 추적을 지속해야 하므로 적절한 새로고침 비율을 설정한다. 이 것은 내부 video_clock 값을 통해 수행하는데 연관된 비디오가 얼마나 많은 시간을 추적하는지 유지함으로서 수행된다. 이 값을 빅 구조체에 추가한다.

typedef struct VideoState {
double video_clock; // 최종 디코드된 프레임의 pts / 다음 디코드된 프레임의 예측되는 pts

synchronize_video 함수이다. 자체로서 꽤 서술적이다.

double synchronize_video(VideoState* is, AVFrame* src_frame, double pts) {
double frame_delay;
if (pts != 0) {
/* 만약 pts를가지면 비디오 클락을 이것으로 설정 */
is->video_clock=pts;
} else {
/* 주어진 pts가 없으면 클락으로 설정
pts = is->video_clock;
}
/* 비디오 클락 업데이트 */
frame_delay = av_q2d(is->video_st->codec->time_base);
/* 만약 프레임 반복하면 적절히 클락을 조율 */
frame_delay += src_frame->repeat_pict * (frame_delay*0.5);
is->video_clock += frame_delay;
return pts;
}

이 함수에서 처리한 반복된 프레임에 대해 더 알게될 것이다.

이제 PTS를 얻고 queue_picture 를 사용하여 프레임을 큐잉하며 새로운 pts 매개변수를 추가하자

// 비디오 프레임을 얻었는가?
if (frameFinished) {
pts = synchronize_video(is, pFrame, pts);
if (queue_picture(is, pFrame, pts) < 0) {
break;
}
}

queue_picture에 대해 바뀐 오직 한가지는 pts값을 큐한 VideoPicture 에 저장한다는 점이다. pts값을 구조체에 더하고 코드를 추가한다.

typedef struct VideoPicture {
...
double pts;
}

int queue_picture(VideoState* is, AVFrame* pFrame, double pts) {
... stuff ...
if (vp->bmp) {
... convert picture ...
vp->pts = pts;
... alert queue ...
}

이제 픽쳐 큐에 픽쳐를 적절한 PTS값과 함께 넣었으니 비디오 새로고침 함수를 살펴보자. 마지막에 새로고침을 80 ms 로 설정한것을 기억할 것이다. 자 이제 이 것을 어떻게 알 수 있을지 살펴보자.

전략은 다음 PTS를 예측하기 위해 간단히 이전 pts와 현재것사이의 시간을 측정하는 것이다. 동시에, 비디오를 오디오에 맞춘다. 오디오 클락을 만들 것이며 : 우리가 재생중인 오디오의 위치를 추적하는 내부적 값, 이 것은 mp3 플레이어의 디지털 리드아웃같은 것이다. 비디오를 오디오에 맞췄으니 비디오 스레드는 이 값을 사용해 너무 앞이나 너무 뒤의 것을 알 수 있게 된다.

나중에 구현을 살펴볼 것이다. 일단 오디오 클락의 시간을 얻는 get_audio_clock함수를 가지고 있다 가정하자. 이 값을 가지면 비디오와 오디오가 싱크가 벗어나면 어떻게 할 것인가? 찾기나 다른 것을 통해 맞는 패킷을 찾아야 할 지모른다. 대신 우리는 다음 새로고침을 위해 미리 계산한 값을 조율한다: 만약 PTS가 오디오 시간보다 너무 먼 뒤쪽이면 계산한 딜레이를 두배로 한다. 만약 pts가 오디오 시간보다 너무 앞쪽이면 가능한 빨리 새로고침을 한다. 이제 우리는 조율한 새로고침 시간 또는 딜레이를 가져 frame_timer 를 실행해 유지한 컴퓨터의 클락과 비교하는데 사용한다. 이 프레임 타이머는 영화를 재생하는 도중 계산한 딜레이 전체의 합산이 된다. 다른 말로, 이 frame_timer는 다음 프레임을 디스플레이할 때 언제가 되어야하는지이다. 새로운 딜레이를 프레임 타이머에 더하고 컴퓨터의 클락에 시간과 비교하며 다음 새로고침에 그 값을 사용한다. 이 부분은 약간 헷갈릴 수 있으니 코드를 자세히 살펴보자.

void video_refresh_timer(void* userdata) {
VideoState* is = (VideoState*)userdata;
VideoPicture* vp;
double actual_delay, delay, sync_threshold, ref_clock, diff;
if (is->video_st) {
if (is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
delay = vp->pts - is->frame_last_pts; /* 마지막 시간으로부터의 pts */
if (delay <= 0 || delay >= 1.0) {
/* 만약 딜레이가 부정확하면 이전 것을 사용 */
delay = is->frame_last_delay;
}
/* 다음 시간을 위해 저장 */
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;

/* 오디오에 싱크하기 위해 딜레이 업데이트 */
ref_clock = get_audio_clock(is);
diff = vp->pts - ref_clock;
/* 프레임 스킵 또는 반복. 딜레이를 받아 FFPlay가 최상의 추측을 모를 때 사용된다. */
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if (fabs(diff) < AV_NOSYNC_THRESHOLD) {
if (diff <= -sync_threshold) {
delay = 0;
} else if (diff >= sync_threshold) {
delay = 2*delay;
}
}
is->frame_timer += delay;
/* 컴퓨터의 실제 딜레이 */
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if (actual_delay < 0.010) {
/* 픽쳐 대신 스킵해야 한다 */
actual_delay = 0.010;
}
schedule_refresh(is, (int)(actual_delay*1000+0.5));
/* 픽쳐를 보여준다! */
video_display(is);
/* 다음 픽쳐를 위해 큐를 업데이트한다 */
if (++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}

몇개의 확인이 있다. 먼저 PTS와 이전 PTS 사이의 딜레이간의 차이이다. 만약 예측된 것이 아니면 마지막 딜레이를 사용한다. 다음으로, 싱크 스레졸드를 가지는데 싱크에서 완벽하게 될 수 있지 않기 때문이다. ffplay는 0.01을 이 값으로 사용한다. 또한 싱크 스레졸드가 절대로 PTS사잇 갭보다 작을 순 없다. 최종적으로, 최소 새로고침 값을 10밀리세컨드로 한다. 이 상황에서는 프레임을 스킵한다.

큰 구조체에 몇가지의 변수를 추가했다. 또한 stream_component_open:에서 프레임 타이머와 이전 프레임 딜레이를 초기화하는 것을 잊지말도록 한다.

is->frame_timer = (double)av_gettime() / 1000000.0;
is->frame_last_delay = 40e-3;

싱킹: 오디오 클락

이제 오디오 클락을 구현할 때이다. 클락 시간은 audio_decode_frame 함수에서 업데이트 할 수 있는데 오디오를 디코드하는 곳이다. 이제, 이 함수를 호출해 모든 새로운 패킷을 전부 처리하지는 않음을 기억하자. 그래서 클락을 업데이트할 두 장소가 있다. 첫 번째 장소는 새로운 패킷을 받는 곳으로서 단순히 패킷의 PTS로 오디오 클락을 설정하는 것이다. 그러면 만약 패킷이 다중 프레임을 갖는다면 샘플의 갯수를 계산하여 오디오 플레이에 시간을 유지하고 주어진 초당 샘플수 비율로 곱한다. 그래서
일단 패킷을 가져오면

/* 만약 업데이트 하면, 오디오 클락을 w/pts로 업데이트한다 */
if (pkt->pts != AV_NOPTS_VALUE) {
is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
}

그리고 패킷을 처리하면

/* 오디오 클락을 최종것으로 유지한다. */
pts = is->audio_clock;
*pts_ptr = pts;
n = 2*is->audio_st->codec->channels;
is->audio_clock+= (double)data_size/ (double)(n*is->audio_st->codec->sample_rate);

몇가지의 세부적인것들: 함수의 템플릿은 pts_ptr을 포함하는 것으로 변경되었다. pts_ptr은 audio_callback에 알릴수있게 오디오 패킷의 pts이다. 이 것은 다음 번에 비디오와 오디오를 동기화할 때 사용한다.

이제 최종적으로 get_audio_clock함수를 구현할 수 있다. is->audio_clock값을 얻는 것 만큼 단순하지는 않다. 처리하는 동안 오디오 PTS를 항상 설정함을 확인한다. 그러나 audio_callback 함수를 보면 오디오 패킷의 모든 데이터를 우리의 출력 버퍼로 전달함을 볼 수 있다. 이 의미는 오디오 클락내의 값은 너무 앞쪽일수 있다는 점이다. 그러니 우리는 쓰기위한게 얼마나 남았는지를 호가인해야 한다. 이제 완전한 코드를 볼 수있다.

double get_audio_clock(VideoState* is) {
double pts;
int hw_buf_size, bytes_per_sec, n;
pts = is->audio_clock; /* 오디오 스레드에서 유지됨 */
hw_buf_size = is->audio_buf_size - is->audio_buf_index;
bytes_per_sec = 0;
n = is->audio_st->codec->channels * 2;
if (is->audio_st) {
bytes_per_sec = is->audio_st->codec->sample_rate *n;
}
if (bytes_per_sec) {
pts -=(double)hw_buf_size/bytes_per_sec;
}
return pts;
}

이제 이함수가 왜 작동하는지 설명할 수 있을 것이다.

여기까지다! 계속해서 컴파일해보자

gcc -o tutorial05 tutorial05.c -lavutil -lavformat -lavcodec -lswscale -lz -lm `sdl-config --clags --libs`

이제 영화 재생기로 영화를 볼 수 있다. 다음번엔 오디오 싱킹을 살펴보고 시킹에 대해서 이야기 할 것이다.











// tutorial05.c
// A pedagogical video player that really works!
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard,
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial05 tutorial05.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed,
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif
#include <stdio.h>
#include <assert.h>
#include <math.h>
// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0
#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 1
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
typedef struct VideoPicture {
SDL_Overlay *bmp;
int width, height; /* source height & width */
int allocated;
double pts;
} VideoPicture;
typedef struct VideoState {
AVFormatContext *pFormatCtx;
int videoStream, audioStream;
double audio_clock;
AVStream *audio_st;
AVCodecContext *audio_ctx;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
int audio_hw_buf_size;
double frame_timer;
double frame_last_pts;
double frame_last_delay;
double video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
AVStream *video_st;
AVCodecContext *video_ctx;
PacketQueue videoq;
struct SwsContext *sws_ctx;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid;
SDL_Thread *video_tid;
char filename[1024];
int quit;
} VideoState;
SDL_Surface *screen;
SDL_mutex *screen_mutex;
/* Since we only have one decoding thread, the Big Struct
can be global in case we need it. */
VideoState *global_video_state;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(global_video_state->quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
double get_audio_clock(VideoState *is) {
double pts;
int hw_buf_size, bytes_per_sec, n;
pts = is->audio_clock; /* maintained in the audio thread */
hw_buf_size = is->audio_buf_size - is->audio_buf_index;
bytes_per_sec = 0;
n = is->audio_ctx->channels * 2;
if(is->audio_st) {
bytes_per_sec = is->audio_ctx->sample_rate * n;
}
if(bytes_per_sec) {
pts -= (double)hw_buf_size / bytes_per_sec;
}
return pts;
}
int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {
int len1, data_size = 0;
AVPacket *pkt = &is->audio_pkt;
double pts;
int n;
for(;;) {
while(is->audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
if(len1 < 0) {
/* if error, skip frame */
is->audio_pkt_size = 0;
break;
}
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
is->audio_ctx->channels,
is->audio_frame.nb_samples,
is->audio_ctx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, is->audio_frame.data[0], data_size);
}
is->audio_pkt_data += len1;
is->audio_pkt_size -= len1;
if(data_size <= 0) {
/* No data yet, get more frames */
continue;
}
pts = is->audio_clock;
*pts_ptr = pts;
n = 2 * is->audio_ctx->channels;
is->audio_clock += (double)data_size /
(double)(n * is->audio_ctx->sample_rate);
/* We have data, return it and come back for more later */
return data_size;
}
if(pkt->data)
av_free_packet(pkt);
if(is->quit) {
return -1;
}
/* next packet */
if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
is->audio_pkt_data = pkt->data;
is->audio_pkt_size = pkt->size;
/* if update, update the audio clock w/pts */
if(pkt->pts != AV_NOPTS_VALUE) {
is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
}
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *)userdata;
int len1, audio_size;
double pts;
while(len > 0) {
if(is->audio_buf_index >= is->audio_buf_size) {
/* We have already sent all our data; get more */
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
if(audio_size < 0) {
/* If error, output silence */
is->audio_buf_size = 1024;
memset(is->audio_buf, 0, is->audio_buf_size);
} else {
is->audio_buf_size = audio_size;
}
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
}
static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0; /* 0 means stop timer */
}
/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}
void video_display(VideoState *is) {
SDL_Rect rect;
VideoPicture *vp;
float aspect_ratio;
int w, h, x, y;
int i;
vp = &is->pictq[is->pictq_rindex];
if(vp->bmp) {
if(is->video_ctx->sample_aspect_ratio.num == 0) {
aspect_ratio = 0;
} else {
aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
is->video_ctx->width / is->video_ctx->height;
}
if(aspect_ratio <= 0.0) {
aspect_ratio = (float)is->video_ctx->width /
(float)is->video_ctx->height;
}
h = screen->h;
w = ((int)rint(h * aspect_ratio)) & -3;
if(w > screen->w) {
w = screen->w;
h = ((int)rint(w / aspect_ratio)) & -3;
}
x = (screen->w - w) / 2;
y = (screen->h - h) / 2;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_LockMutex(screen_mutex);
SDL_DisplayYUVOverlay(vp->bmp, &rect);
SDL_UnlockMutex(screen_mutex);
}
}
void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
double actual_delay, delay, sync_threshold, ref_clock, diff;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
delay = vp->pts - is->frame_last_pts; /* the pts from last time */
if(delay <= 0 || delay >= 1.0) {
/* if incorrect delay, use previous one */
delay = is->frame_last_delay;
}
/* save for next time */
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;
/* update delay to sync to audio */
ref_clock = get_audio_clock(is);
diff = vp->pts - ref_clock;
/* Skip or repeat the frame. Take delay into account
FFPlay still doesn't "know if this is the best guess." */
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <= -sync_threshold) {
delay = 0;
} else if(diff >= sync_threshold) {
delay = 2 * delay;
}
}
is->frame_timer += delay;
/* computer the REAL delay */
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010) {
/* Really it should skip the picture instead */
actual_delay = 0.010;
}
schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
/* show the picture! */
video_display(is);
/* update queue for next picture! */
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
void alloc_picture(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
vp = &is->pictq[is->pictq_windex];
if(vp->bmp) {
// we already have one make another, bigger/smaller
SDL_FreeYUVOverlay(vp->bmp);
}
// Allocate a place to put our YUV image on that screen
SDL_LockMutex(screen_mutex);
vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
is->video_ctx->height,
SDL_YV12_OVERLAY,
screen);
SDL_UnlockMutex(screen_mutex);
vp->width = is->video_ctx->width;
vp->height = is->video_ctx->height;
vp->allocated = 1;
}
int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {
VideoPicture *vp;
int dst_pix_fmt;
AVPicture pict;
/* wait until we have space for a new pic */
SDL_LockMutex(is->pictq_mutex);
while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
!is->quit) {
SDL_CondWait(is->pictq_cond, is->pictq_mutex);
}
SDL_UnlockMutex(is->pictq_mutex);
if(is->quit)
return -1;
// windex is set to 0 initially
vp = &is->pictq[is->pictq_windex];
/* allocate or resize the buffer! */
if(!vp->bmp ||
vp->width != is->video_ctx->width ||
vp->height != is->video_ctx->height) {
SDL_Event event;
vp->allocated = 0;
alloc_picture(is);
if(is->quit) {
return -1;
}
}
/* We have a place to put our picture on the queue */
if(vp->bmp) {
SDL_LockYUVOverlay(vp->bmp);
vp->pts = pts;
dst_pix_fmt = PIX_FMT_YUV420P;
/* point pict at the queue */
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
// Convert the image into YUV format that SDL uses
sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, is->video_ctx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(vp->bmp);
/* now we inform our display thread that we have a pic ready */
if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_windex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size++;
SDL_UnlockMutex(is->pictq_mutex);
}
return 0;
}
double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {
double frame_delay;
if(pts != 0) {
/* if we have pts, set video clock to it */
is->video_clock = pts;
} else {
/* if we aren't given a pts, set it to the clock */
pts = is->video_clock;
}
/* update the video clock */
frame_delay = av_q2d(is->video_ctx->time_base);
/* if we are repeating a frame, adjust clock accordingly */
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
int video_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVPacket pkt1, *packet = &pkt1;
int frameFinished;
AVFrame *pFrame;
double pts;
pFrame = av_frame_alloc();
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
// means we quit getting packets
break;
}
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
// means we quit getting packets
break;
}
pts = 0;
// Decode video frame
avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);
if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
pts = 0;
}
pts *= av_q2d(is->video_st->time_base);
// Did we get a video frame?
if(frameFinished) {
pts = synchronize_video(is, pFrame, pts);
if(queue_picture(is, pFrame, pts) < 0) {
break;
}
}
av_free_packet(packet);
}
av_frame_free(&pFrame);
return 0;
}
int stream_component_open(VideoState *is, int stream_index) {
AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx = NULL;
AVCodec *codec = NULL;
SDL_AudioSpec wanted_spec, spec;
if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}
codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
if(!codec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1; // Error copying codec context
}
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
// Set audio settings from codec info
wanted_spec.freq = codecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = codecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
is->audio_hw_buf_size = spec.size;
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
switch(codecCtx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_ctx = codecCtx;
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];
is->video_ctx = codecCtx;
is->frame_timer = (double)av_gettime() / 1000000.0;
is->frame_last_delay = 40e-3;
packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread(video_thread, is);
is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
is->video_ctx->pix_fmt, is->video_ctx->width,
is->video_ctx->height, PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL
);
break;
default:
break;
}
}
int decode_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVFormatContext *pFormatCtx;
AVPacket pkt1, *packet = &pkt1;
int video_index = -1;
int audio_index = -1;
int i;
is->videoStream=-1;
is->audioStream=-1;
global_video_state = is;
// Open video file
if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
return -1; // Couldn't open file
is->pFormatCtx = pFormatCtx;
// Retrieve stream information
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1; // Couldn't find stream information
// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, is->filename, 0);
// Find the first video stream
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
video_index < 0) {
video_index=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audio_index < 0) {
audio_index=i;
}
}
if(audio_index >= 0) {
stream_component_open(is, audio_index);
}
if(video_index >= 0) {
stream_component_open(is, video_index);
}
if(is->videoStream < 0 || is->audioStream < 0) {
fprintf(stderr, "%s: could not open codecs\n", is->filename);
goto fail;
}
// main decode loop
for(;;) {
if(is->quit) {
break;
}
// seek stuff goes here
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay(10);
continue;
}
if(av_read_frame(is->pFormatCtx, packet) < 0) {
if(is->pFormatCtx->pb->error == 0) {
SDL_Delay(100); /* no error; wait for user input */
continue;
} else {
break;
}
}
// Is this a packet from the video stream?
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet(packet);
}
}
/* all done - wait for it */
while(!is->quit) {
SDL_Delay(100);
}
fail:
if(1){
SDL_Event event;
event.type = FF_QUIT_EVENT;
event.user.data1 = is;
SDL_PushEvent(&event);
}
return 0;
}
int main(int argc, char *argv[]) {
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
// Register all formats and codecs
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
// Make a screen to put our video
#ifndef __DARWIN__
screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
screen_mutex = SDL_CreateMutex();
av_strlcpy(is->filename, argv[1], sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
schedule_refresh(is, 40);
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
for(;;) {
SDL_WaitEvent(&event);
switch(event.type) {
case FF_QUIT_EVENT:
case SDL_QUIT:
is->quit = 1;
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
return 0;
}
    

덧글

댓글 입력 영역