FFmpeg and SDL Tutorial - Seeking FFmpeg


Tutorial 07: Seeking


탐색 명령 다루기

이제 재생기에 탐색 기능을 추가한다. 또한 av_seek_frame 이 얼마나 쉽게 사용할 수 있는지 확인한다.

작게 앞뒤로 가는 것과 많이 앞뒤로 가는 것을 만들 것이다. 왼쪽 오른쪽 방향은 10초, 위 아래 방향은 60초이다. 그러니 먼저 메인루프에 키 입력을 받도록 처리한다. 키입력을 받은 곳에서 직접적으로 av_seek_frame을 호출할 수는 없다. 이 것은 decode_thread루프에서 처리해야한다. 그러니 큰 구조체에 추가적인 변수를 추가하여 탐색 플래그를 설정한다.

int seek_req;
int seek_flags;
int64_t seek_pos;

이제 메인루프를 설정해 키입력을 받는다.

for (;;) {
double incr, pos;
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_LEFT:
incr=-10;
goto do_seek;
case SDLK_RIGHT:
incr = 10;
goto do_seek;
case SDLK_UP:
incr = 60;
goto do_seek;
case SDLK_DOWN:
incr = -60;
goto do_seek;
do_seek:
if (global_video_state) {
pos = get_master_clock(global_video_state);
pos += incr;
stream_seek(global_video_state, (int64_t)(pos*AV_TIME_BASE), incr);
}
break;
default:
break;
}
break;

키 입력을 탐지하기 위해 SDL_KEYDOWN이벤트를 받는다. 그러면 event.key.keysym.sym 을 사용해 어떤 키가 들어왔는지 확인한다. 일단 어떤 것인지 확인되면 새로운 get_master_clock 함수에서 값을 얻어 증분을 더한다. 그러면 stream_seek 함수를 호출해 seek_pos와 같은 값을 설정한다. 새로운 시간 값을 avcodec의 내부 타임스탬프 유닛에 변환한다. 스트림내의 타임스탬프를 리콜은 초가 아닌 프레임으로 측정됨을 되새기자. 공식 seconds=frames*time_base(fps) 를 사용한다. avcodec 은 1000000 fps값을 기본으로 한다. (그래서 2초의 pos는 타임스탬프에서 2000000이다.) 이 값을 컨버트해야하는 이유는 이후에 보게될 것이다.

여기에 stream_seek 함수가 있다. 뒤로 갈때는 플래그를 설정한다.

void stream_seek(VideoState* is, int64_t pos, int rel) {
if (!is->seek_req) {
is->seek_pos = pos;
is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
is->seek_req = 1;
}
}

이제 우리의 decode_thread를 확인해 실제적인 탐색을 어떻게 수행할지 살펴보자. 소스파일에서 "seek stuff goes here" 부분으로 표시되어 있다. 자 여기에 추가해보자.

탐색의 중심에는 av_seek_frame 함수이다. 이 함수는 포맷 컨텍스트, 스트림, 타임스탬프, 그리고 매개변수로 플래그 집합이있다. 함수는 우리가 준 타임스탬프로 탐색할 것이다. 타임스탬프의 단위는 함수에 건내는 스트림의 time_base이다. 그러나, 스트림은 전달하지 않아도 된다. (-1 값으로) 그렇게 하면 time_base는 avcodec의 내부 타임 스탬프 단위이거나 1000000fps가 된다. 이 것이 seek_pos를 설정했을 때 AV_TIME_BASE로 곱해지는 이유이다.

그러나, 스트림에 -1을 할당해 av_seek_frame을 호출했을 때 아주 간혹 에러가 발생할 수 있으므로 파일의 첫 스트림을 전달하도록 한다. 새로운 유닛에 맞게 타임스탬프를 리스케일해야함도 잊지말자.

if (is->seek_req) {
int stream_index = -1;
int64_t seek_target = is->seek_pos;
if (is->videoStream >= 0) stream_index = is->videoStream;
else if (is->audioStream >= 0) stream_index = is->audioStream;

if (stream_index >= 0) {
seek_target = av_rescale_q(seek_target, AV_TIME_BASE_Q, pFormatCtx->streams[stream_index]->time_base);
}
if (av_seek_frame(is->pFormatCtx, stream_index, seek_target, is->seek_flags) < 0) {
fprintf(stderr, "%s: error while seeking\n", is->pFormatCtx->filename);
} else {
/* 패킷 큐를 다룬다. 이후에 더 많이 */

av_rescale_q(a,b,c) 는 타임스탬프를 한 베이스에서 다른 것으로 리스캐일한다. 기본적으로 a*b/c를 계산하지만 이 함수는 계산에서 오버플로우가 발생할 수 있기 때문에 필수적이다. AV_TIME_BASE_Q는 AV_TIME_BASE의 프랙셔널 버전이다. 이들은 꽤 다른데 AV_TIME_BASE*time_in_seconds = avcodec_timestamp와 AV_TIME_BASE_Q*avcodec_timestamp = time_in_seconds (하지만 AV_TIME_BASE_Q는 실제로는 AVRational 객체로서 다루기 위해서는 특별한 q함수를 사용해야만 한다.)

버퍼 플러싱

맞게 탐색했지만 아직 끝난게 아니다. 패킷을 합치기 위해 큐를 설정했음을 기억하자. 이제 그 큐나 영화가 탐색하지 않을 것을 플러시해야 한다. 뿐만아니라 avcodec은 자체적인 내부 버퍼가 있어 각 스레드에서 플러시 해야 한다.

이를 위해, 우리의 패킷 큐를 제거하기 위한 함수를 작성한다. 그러면 오디오와 비디오 스레드에 명령을 건내어 avcodec의 내부 버퍼를 플러시 하게 한다. 큐에 특별한 패킷을 넣어 이룰 수행하며 이 특별한 패킷을 감지해 이들 버퍼를 플러시한다.

플러시 함수부터 시작이다. 이는 꽤 간단하니 그냥 코드를 보자

static void packet_queue_flush(PacketQueue* q) {
AVPacketList* pkt, *pkt1;
SDL_LockMutex(q->mutex);
for (pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
pkt1 = pkt->next;
av_free_packet(&pkt->pkt);
av_freep(&pkt);
}
q->last_pkt = NULL;
q->first_pkt = NULL;
q->nb_packets = 0;
q->size = 0;
SDL_UnlockMutex(q->mutex);
}

이제 큐가 플러시뒤었다. 플러시 패킷을 할당하자. 먼저 무엇인지 정의하고 생성하자

AVPacket flush_pkt;
main() {
...
av_init_packet(&flush_pkt);
flush_pkt.data = "FLUSH";
...
}

이제 이 패킷을 큐에 넣는다.

} else {
if (is->audioStream >= 0) {
packet_queue_flush(&is->audioq);
packet_queue_put(&is->audioq, &flush_pkt);
}
if (is->videoStream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}
}
is->seek_req = 0;

(이 코드 조각은 위의 decode_thread에서 연속된 부분이다. 그러니 packet_queue_put 을 변경하여 특별한 플러시 패킷이 중첩되지 않게 한다.

int packet_queue_put(PacketQueue* q, AVPacket* pkt) {
AVPacketList* pkt1;
if (pkt != &flush_pkt && av_dup_packet(pkt) < 0) {
return -1;
}

그리고 오디오 스레드와 비디오 스레드에서 packet_queue_get이후에 avcodec_flush_buffers를 즉시 호출하게 한다.

if (packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
if (pkt->data == flush_pkt.data) {
avcodec_flush_buffers(is->audio_st->codec);
continue;
}

위 코드 스닙샷은 비디오 스레드에서도 정확히 같으며 audio대신 video로 대체한다.

여기 까지다. 컴파일 해본다.

gcc -o tutorial07 tutorial07.c -lavutil -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
이제 1000라인도 안되는 C코드로 만들어진 영화재생기를 즐길때이다!

물론 여기에 추가할만한 많은 부분이 있다.

이제는?

이제 작동하는 플레이어를 갖게 됐다. 하지만 더 나아질 수 있다. 추가할 수 있는 많은 기능이 있다.

- 이 재생기는 허접하다. 기반으로한 ffplay.c 의 버전이 완전히 달라졌고 이 튜토리얼은 결과적으로 달라져야 한다. 만약 FFmpeg라이브러리를 사용한 더 세부적인 것을 원한다면 다음 작업으로 최근 버전의 ffplay.c를 확인하기를 바란다.
- 에러 핸들링. 이 코드에서의 에러핸들링은 부족하다.
- 포징. 영화를 일시정지할수 없다. 이는 내부 포즈드 변수를 큰 구조체에 추가해 사용자가 일시정지함을 처리한다. 그러면 오디오, 비디오, 와 디코드 스레드는 아무것도 하지 않는다. av_read_play를 사용해 네트워크 지원도 할 수 있다. 설명하기 매우 심플하지만 자체적으로 알기는 명백하지 않다. ffplay.c를 확인한다.
- 비디오 하드웨어 지원
- 바이트에 의한 탐색. 초를 대신해 바이트로도 탐색위치를 계산할 수 있다. 이는 VOB파일과 같은 비연속적 타임스탬프를 가진 비디오 파일에서 더 정확하다.
- 프레임 드랍. 만약 비디오가 너무 뒤에 떨어져있으면 짧은 새로고침을 설정하는 대신에 다음 프레임을 드랍해야 한다.
- 네트워크 지원. 이 비디오 재생기는 네트워크 스트리밍 비디오는 재생할 수 없다.
- YUV파일과 같은 로비디오에대한 지원. YUV파일과 같은 로 비디오와 같은 지원을위해 몇가지의 선택사항있는데, 사이즈나 time_base를 가늠할수 없기 때문이다.
- 전체화면
- 다양한 옵션.. 다른 픽 포맷; ffplay.c의 모든 커맨드라인 스위치를 확인한다.

우리는 일부만 살펴봤지만 FFmpeg에 대해 더 많은것을 알고 싶으면, 다음 단계는 멀티미디어 인코드 부분이 될 것이다. FFmpeg 배포에서output_example.c 파일이 좋은 시작점이 될 것이다.

업데이트
이를 업데이트한지 오래 되었지만. 비디오 소프트웨어 세계에서 많은 발전이 있었다. 이 튜토리얼은 오직 단순 API업데이트만 필요로한다. 기본 개념에서 용어중에 아주작은 부분만이 변경되었다. 이들 대부분의 업데이트는 코드를 단순화하는 부분에 있다. 그러나, 여기에 업데이트한 코드는 ffplay는 여전히 완전히 이 장난감 플레이어를 넘어선다. 솔직히, 이 플레이어는 실제 영화재생기로는 사용하기 어렵다. 그러니 이 튜토리얼을 발전하고 싶다면 ffplay로 가서 어떤 부분이 빠졌는지 확인해 본다. 내 추측은 비디오 하드웨어 부분이 될 것이지만 다른 명백한 부분에서 그랬을 수도 있다. 

하지만 이 것이 여전히 많은 사람들에게 도움을 준다는 사실에 자랑스럽다. 내가 8년 전에 작성한 많은 함수를 교체해준 chelyaev에가 완전한 감사를 드린다.

자 이 튜토리얼이 도움되고 재미있었으면한다. 만약 제안, 버그, 불만, 찬사등등 원한다면 이메일을 보내주기바란다. Please do not ask me for help in your other ffmpeg projects. I get way too many of those emails.






// 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 SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20
#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 1
#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER
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;
int av_sync_type;
double external_clock; /* external clock base */
int64_t external_clock_time;
int seek_req;
int seek_flags;
int64_t seek_pos;
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 audio_diff_cum; /* used for AV difference average computation */
double audio_diff_avg_coef;
double audio_diff_threshold;
int audio_diff_avg_count;
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
double video_current_pts; ///<current displayed pts (different from video_clock if frame fifos are used)
int64_t video_current_pts_time; ///<time (av_gettime) at which we updated video_current_pts - used to have running video pts
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;
enum {
AV_SYNC_AUDIO_MASTER,
AV_SYNC_VIDEO_MASTER,
AV_SYNC_EXTERNAL_MASTER,
};
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;
AVPacket flush_pkt;
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(pkt != &flush_pkt && 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;
}
static void packet_queue_flush(PacketQueue *q) {
AVPacketList *pkt, *pkt1;
SDL_LockMutex(q->mutex);
for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
pkt1 = pkt->next;
av_free_packet(&pkt->pkt);
av_freep(&pkt);
}
q->last_pkt = NULL;
q->first_pkt = NULL;
q->nb_packets = 0;
q->size = 0;
SDL_UnlockMutex(q->mutex);
}
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;
}
double get_video_clock(VideoState *is) {
double delta;
delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
return is->video_current_pts + delta;
}
double get_external_clock(VideoState *is) {
return av_gettime() / 1000000.0;
}
double get_master_clock(VideoState *is) {
if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
return get_video_clock(is);
} else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
return get_audio_clock(is);
} else {
return get_external_clock(is);
}
}
/* Add or subtract samples to get a better sync, return new
audio buffer size */
int synchronize_audio(VideoState *is, short *samples,
int samples_size, double pts) {
int n;
double ref_clock;
n = 2 * is->audio_ctx->channels;
if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
double diff, avg_diff;
int wanted_size, min_size, max_size /*, nb_samples */;
ref_clock = get_master_clock(is);
diff = get_audio_clock(is) - ref_clock;
if(diff < AV_NOSYNC_THRESHOLD) {
// accumulate the diffs
is->audio_diff_cum = diff + is->audio_diff_avg_coef
* is->audio_diff_cum;
if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
is->audio_diff_avg_count++;
} else {
avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
if(fabs(avg_diff) >= is->audio_diff_threshold) {
wanted_size = samples_size + ((int)(diff * is->audio_ctx->sample_rate) * n);
min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100);
max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100);
if(wanted_size < min_size) {
wanted_size = min_size;
} else if (wanted_size > max_size) {
wanted_size = max_size;
}
if(wanted_size < samples_size) {
/* remove samples */
samples_size = wanted_size;
} else if(wanted_size > samples_size) {
uint8_t *samples_end, *q;
int nb;
/* add samples by copying final sample*/
nb = (samples_size - wanted_size);
samples_end = (uint8_t *)samples + samples_size - n;
q = samples_end + n;
while(nb > 0) {
memcpy(q, samples_end, n);
q += n;
nb -= n;
}
samples_size = wanted_size;
}
}
}
} else {
/* difference is TOO big; reset diff stuff */
is->audio_diff_avg_count = 0;
is->audio_diff_cum = 0;
}
}
return samples_size;
}
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;
}
if(pkt->data == flush_pkt.data) {
avcodec_flush_buffers(is->audio_ctx);
continue;
}
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 {
audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,
audio_size, pts);
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];
is->video_current_pts = vp->pts;
is->video_current_pts_time = av_gettime();
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 if not master source */
if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
ref_clock = get_master_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 = av_frame_get_best_effort_timestamp(pFrame);
} else {
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;
is->video_current_pts_time = av_gettime();
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->seek_req) {
int stream_index= -1;
int64_t seek_target = is->seek_pos;
if (is->videoStream >= 0) stream_index = is->videoStream;
else if(is->audioStream >= 0) stream_index = is->audioStream;
if(stream_index>=0){
seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q,
pFormatCtx->streams[stream_index]->time_base);
}
if(av_seek_frame(is->pFormatCtx, stream_index,
seek_target, is->seek_flags) < 0) {
fprintf(stderr, "%s: error while seeking\n",
is->pFormatCtx->filename);
} else {
if(is->audioStream >= 0) {
packet_queue_flush(&is->audioq);
packet_queue_put(&is->audioq, &flush_pkt);
}
if(is->videoStream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}
}
is->seek_req = 0;
}
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;
}
void stream_seek(VideoState *is, int64_t pos, int rel) {
if(!is->seek_req) {
is->seek_pos = pos;
is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
is->seek_req = 1;
}
}
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->av_sync_type = DEFAULT_AV_SYNC_TYPE;
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
av_init_packet(&flush_pkt);
flush_pkt.data = "FLUSH";
for(;;) {
double incr, pos;
SDL_WaitEvent(&event);
switch(event.type) {
case SDL_KEYDOWN:
switch(event.key.keysym.sym) {
case SDLK_LEFT:
incr = -10.0;
goto do_seek;
case SDLK_RIGHT:
incr = 10.0;
goto do_seek;
case SDLK_UP:
incr = 60.0;
goto do_seek;
case SDLK_DOWN:
incr = -60.0;
goto do_seek;
do_seek:
if(global_video_state) {
pos = get_master_clock(global_video_state);
pos += incr;
stream_seek(global_video_state, (int64_t)(pos * AV_TIME_BASE), incr);
}
break;
default:
break;
}
break;
case FF_QUIT_EVENT:
case SDL_QUIT:
is->quit = 1;
/*
* If the video has finished playing, then both the picture and
* audio queues are waiting for more data. Make them stop
* waiting and terminate normally.
*/
SDL_CondSignal(is->audioq.cond);
SDL_CondSignal(is->videoq.cond);
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
return 0;
}
    
    

덧글

댓글 입력 영역