From 183aaa802b19fe2d7a27593e5e76acbe451b9b76 Mon Sep 17 00:00:00 2001 From: Ali Gholami Rudi Date: Tue, 12 Apr 2011 21:34:45 +0430 Subject: [PATCH] move ffmpeg dependent parts to ffs This patch does a lot of things: * move ffmpeg-dependent parts to ffs.c * separate decoding video from audio (needed to remove audio delay) * using oss instead of alsa * moving audio decoding to a separate thread --- Makefile | 6 +- fbff.c | 303 ++++++++++++++++++++++++++++----------------------------------- ffs.c | 202 ++++++++++++++++++++++++++++++++++++++++++ ffs.h | 19 ++++ 4 files changed, 358 insertions(+), 172 deletions(-) create mode 100644 ffs.c create mode 100644 ffs.h diff --git a/Makefile b/Makefile index a6707bb..809dd81 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ CC = cc -CFLAGS = -Wall -Os -LDFLAGS = -lavutil -lavformat -lavcodec -lavutil -lswscale -lasound -lz -lm +CFLAGS = -Wall -O2 +LDFLAGS = -lavutil -lavformat -lavcodec -lavutil -lswscale -lz -lm -lpthread all: fbff .c.o: $(CC) -c $(CFLAGS) $< -fbff: fbff.o draw.o +fbff: fbff.o ffs.o draw.o $(CC) -o $@ $^ $(LDFLAGS) clean: rm -f *.o fbff diff --git a/fbff.c b/fbff.c index c47be75..a8bd505 100644 --- a/fbff.c +++ b/fbff.c @@ -1,5 +1,5 @@ /* - * fbff - a small ffmpeg-based framebuffer/alsa media player + * fbff - a small ffmpeg-based framebuffer/oss media player * * Copyright (C) 2009-2011 Ali Gholami Rudi * @@ -7,42 +7,32 @@ */ #include #include +#include #include #include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include #include -#include #include "config.h" +#include "ffs.h" #include "draw.h" -static AVFormatContext *fc; -static AVFrame *frame; -static struct SwsContext *swsc; - -static int vsi = -1; /* video stream index */ -static AVCodecContext *vcc; /* video codec context */ -static AVCodec *vc; /* video codec */ - -static int asi = -1; /* audio stream index */ -static AVCodecContext *acc; /* audio codec context */ -static AVCodec *ac; /* audio codec */ +#define FF_PLAY 0 +#define FF_PAUSE 1 +#define FF_EXIT 2 -static int seek_idx; /* stream index used for seeking */ -static int pos_cur; /* current frame number in seek_idx stream */ -static int pos_max; /* maximum frame number seen so far */ static int frame_jmp = 1; /* the changes to pos_cur for each frame */ +static int afd; /* oss fd */ -static snd_pcm_t *alsa; -static int bps; /* bytes per sample */ static int arg; static struct termios termios; -static unsigned long num; /* decoded video frame number */ static int cmd; -static long last_ts; static float zoom = 1; static int magnify = 0; @@ -52,84 +42,56 @@ static int audio = 1; static int video = 1; static int just = 0; -static void init_streams(void) -{ - int i; - for (i = 0; i < fc->nb_streams; i++) { - if (fc->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) - vsi = i; - if (fc->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) - asi = i; - } - if (video && vsi != -1) { - vcc = fc->streams[vsi]->codec; - vc = avcodec_find_decoder(vcc->codec_id); - avcodec_open(vcc, vc); - } - if (audio && asi != -1) { - acc = fc->streams[asi]->codec; - ac = avcodec_find_decoder(acc->codec_id); - avcodec_open(acc, ac); - } - seek_idx = vcc ? vsi : asi; -} +static struct ffs *affs; /* audio ffmpeg stream */ +static struct ffs *vffs; /* video ffmpeg stream */ -static void draw_frame(void) +static void draw_frame(fbval_t *img, int linelen) { + int w, h; fbval_t buf[1 << 14]; - int r, c; - int nr = MIN(vcc->height * zoom, fb_rows() / magnify); - int nc = MIN(vcc->width * zoom, fb_cols() / magnify); - int cb = just ? fb_cols() - nc * magnify : 0; - int i; + int nr, nc, cb; + int i, r, c; + ffs_vinfo(vffs, &w, &h); + nr = MIN(h * zoom, fb_rows() / magnify); + nc = MIN(w * zoom, fb_cols() / magnify); + cb = just ? fb_cols() - nc * magnify : 0; for (r = 0; r < nr; r++) { - unsigned char *row = frame->data[0] + r * frame->linesize[0]; + fbval_t *row = (void *) img + r * linelen; if (magnify == 1) { - fb_set(r, cb, (void *) row, nc); + fb_set(r, cb, row, nc); continue; } - for (c = 0; c < nc; c++) { - fbval_t v = *(fbval_t *) (row + c * 2); + for (c = 0; c < nc; c++) for (i = 0; i < magnify; i++) - buf[c * magnify + i] = v; - } + buf[c * magnify + i] = row[c]; for (i = 0; i < magnify; i++) fb_set(r * magnify + i, cb, buf, nc * magnify); } } -static void decode_video_frame(AVFrame *main_frame, AVPacket *packet) +#define ABUFSZ (1 << 18) +#define BUFS (1 << 6) +static int a_cons; +static int a_prod; +static char a_buf[BUFS][ABUFSZ]; +static int a_len[BUFS]; +static int a_reset; + +static int a_conswait(void) { - int fine = 0; - avcodec_decode_video2(vcc, main_frame, &fine, packet); - if (fine && (!jump || !(num % jump))) { - sws_scale(swsc, main_frame->data, main_frame->linesize, - 0, vcc->height, frame->data, frame->linesize); - draw_frame(); - } + return a_cons == a_prod; } -#define AUDIOBUFSIZE (1 << 20) +static int a_prodwait(void) +{ + return ((a_prod + 1) & (BUFS - 1)) == a_cons; +} -static void decode_audio_frame(AVPacket *pkt) +static void a_doreset(int pause) { - char buf[AUDIOBUFSIZE]; - AVPacket tmppkt; - tmppkt.size = pkt->size; - tmppkt.data = pkt->data; - while (tmppkt.size > 0) { - int size = sizeof(buf); - int len = avcodec_decode_audio3(acc, (int16_t *) buf, - &size, &tmppkt); - if (len < 0) - break; - if (size <= 0) - continue; - if (snd_pcm_writei(alsa, buf, size / bps) < 0) - snd_pcm_prepare(alsa); - tmppkt.size -= len; - tmppkt.data += len; - } + a_reset = 1 + pause; + while (audio && a_reset) + usleep(1000); } static int readkey(void) @@ -157,19 +119,19 @@ static int ffarg(void) static void ffjmp(int n, int rel) { - pos_cur = rel ? pos_cur + n * frame_jmp : pos_cur * n / 100; - if (pos_cur < 0) - pos_cur = 0; - if (pos_cur > pos_max) - pos_max = pos_cur; - av_seek_frame(fc, seek_idx, pos_cur, - frame_jmp == 1 ? AVSEEK_FLAG_FRAME : 0); - last_ts = 0; + struct ffs *ffs = video ? vffs : affs; + long pos = ffs_pos(ffs, n); + a_doreset(0); + if (audio) + ffs_seek(affs, pos, frame_jmp); + if (video) + ffs_seek(vffs, pos, frame_jmp); } static void printinfo(void) { - printf("fbff: %d\t%d \r", pos_cur, pos_cur * 100 / pos_max); + struct ffs *ffs = video ? vffs : affs; + printf("fbff: %8lx\r", ffs_pos(ffs, 0)); fflush(stdout); } @@ -177,10 +139,6 @@ static void printinfo(void) #define JMP2 (JMP1 << 3) #define JMP3 (JMP2 << 5) -#define FF_PLAY 0 -#define FF_PAUSE 1 -#define FF_EXIT 2 - static void execkey(void) { int c; @@ -228,78 +186,87 @@ static void execkey(void) } } -static long ts_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000 + tv.tv_usec / 1000; -} +#define MAXAVDIFF 120 -static void wait(long ts, int vdelay) +static int is_vsync(void) { - int nts = ts_ms(); - if (ts + vdelay > nts) - usleep((ts + vdelay - nts) * 1000); + return !audio || ffs_seq(vffs, 1) + MAXAVDIFF < ffs_seq(affs, 1); } static void read_frames(void) { - AVFrame *main_frame = avcodec_alloc_frame(); - AVPacket pkt; - uint8_t *buf; - int n = AUDIOBUFSIZE; - if (vcc) - n = avpicture_get_size(FFMPEG_PIXFMT, vcc->width * zoom, - vcc->height * zoom); - buf = av_malloc(n * sizeof(uint8_t)); - if (vcc) - avpicture_fill((AVPicture *) frame, buf, FFMPEG_PIXFMT, - vcc->width * zoom, vcc->height * zoom); - while (cmd != FF_EXIT && av_read_frame(fc, &pkt) >= 0) { + while (cmd != FF_EXIT) { execkey(); if (cmd == FF_PAUSE) { + a_doreset(1); waitkey(); continue; } - if (vcc && pkt.stream_index == vsi) { - if (!audio && last_ts) { - AVRational *r = &fc->streams[vsi]->time_base; - int vdelay = 1000 * r->num / r->den; - wait(last_ts, vdelay); + while (audio && !a_prodwait()) { + int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ); + if (ret < 0) + goto eof; + if (ret > 0) { + a_len[a_prod] = ret; + a_prod = (a_prod + 1) & (BUFS - 1); } - last_ts = ts_ms(); - decode_video_frame(main_frame, &pkt); - num++; } - if (acc && pkt.stream_index == asi) - decode_audio_frame(&pkt); - if (pkt.stream_index == seek_idx) { - pos_cur += frame_jmp; - if (pos_cur > pos_max) - pos_max = pos_cur; + if (video && is_vsync()) { + int ignore = jump && !(ffs_seq(vffs, 0) % (jump + 1)); + char *buf; + int ret = ffs_vdec(vffs, ignore ? NULL : &buf); + if (ret < 0) + goto eof; + if (ret > 0) + draw_frame((void *) buf, ret); + ffs_wait(vffs); } - av_free_packet(&pkt); } - av_free(buf); - av_free(main_frame); +eof: + cmd = FF_EXIT; + a_doreset(0); } -#define ALSADEV "default" +static void oss_init(void) +{ + int rate, ch, bps; + afd = open("/dev/dsp", O_RDWR); + if (afd < 0) { + fprintf(stderr, "cannot open /dev/dsp\n"); + exit(1); + } + ffs_ainfo(affs, &rate, &bps, &ch); + ioctl(afd, SOUND_PCM_WRITE_RATE, &rate); + ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch); + ioctl(afd, SOUND_PCM_WRITE_BITS, &bps); +} -static void alsa_init(void) +static void oss_close(void) { - int format = SND_PCM_FORMAT_S16_LE; - if (snd_pcm_open(&alsa, ALSADEV, SND_PCM_STREAM_PLAYBACK, 0) < 0) - return; - snd_pcm_set_params(alsa, format, SND_PCM_ACCESS_RW_INTERLEAVED, - acc->channels, acc->sample_rate, 1, 500000); - bps = acc->channels * snd_pcm_format_physical_width(format) / 8; - snd_pcm_prepare(alsa); + close(afd); } -static void alsa_close(void) +static void *process_audio(void *dat) { - snd_pcm_close(alsa); + oss_init(); + while (1) { + while (!a_reset && (a_conswait() || cmd == FF_PAUSE)) { + if (cmd == FF_EXIT) + goto ret; + usleep(1000); + } + if (a_reset) { + if (a_reset == 1) + a_cons = a_prod; + a_reset = 0; + continue; + } + write(afd, a_buf[a_cons], a_len[a_cons]); + a_cons = (a_cons + 1) & (BUFS - 1); + } +ret: + oss_close(); + return NULL; } static void term_setup(void) @@ -362,46 +329,44 @@ static void read_args(int argc, char *argv[]) int main(int argc, char *argv[]) { + pthread_t a_thread; + char *path = argv[argc - 1]; if (argc < 2) { printf("usage: %s [options] filename\n", argv[0]); return 1; } read_args(argc, argv); - av_register_all(); - if (av_open_input_file(&fc, argv[argc - 1], NULL, 0, NULL)) - return 1; - if (av_find_stream_info(fc) < 0) + ffs_globinit(); + if (video && !(vffs = ffs_alloc(path, 1))) + video = 0; + if (audio && !(affs = ffs_alloc(path, 0))) + audio = 0; + if (!video && !audio) return 1; - init_streams(); - frame = avcodec_alloc_frame(); - if (acc) - alsa_init(); - if (vcc) { + if (audio) + pthread_create(&a_thread, NULL, process_audio, NULL); + if (video) { + int w, h; fb_init(); + ffs_vinfo(vffs, &w, &h); if (!magnify) - magnify = fb_cols() / vcc->width / zoom; + magnify = fb_cols() / w / zoom; if (fullscreen) - zoom = (float) fb_cols() / vcc->width / magnify; - swsc = sws_getContext(vcc->width, vcc->height, vcc->pix_fmt, - vcc->width * zoom, vcc->height * zoom, - FFMPEG_PIXFMT, SWS_FAST_BILINEAR | SWS_CPU_CAPS_MMX2, - NULL, NULL, NULL); + zoom = (float) fb_cols() / w / magnify; + ffs_vsetup(vffs, zoom, FFMPEG_PIXFMT); } term_setup(); signal(SIGCONT, sigcont); read_frames(); term_cleanup(); - if (vcc) { + if (video) { fb_free(); - sws_freeContext(swsc); - avcodec_close(vcc); + ffs_free(vffs); } - if (acc) { - alsa_close(); - avcodec_close(acc); + if (audio) { + pthread_join(a_thread, NULL); + ffs_free(affs); } - av_free(frame); - av_close_input_file(fc); return 0; } diff --git a/ffs.c b/ffs.c new file mode 100644 index 0000000..c65260f --- /dev/null +++ b/ffs.c @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ffs.h" + +/* ffmpeg stream */ +struct ffs { + AVCodecContext *cc; + AVFormatContext *fc; + AVPacket pkt; + int si; /* stream index */ + long ts; /* frame timestamp (ms) */ + long seq; /* frame number among packets of this stream */ + long allseq; /* frame number among all packets */ + + /* decoding video frames */ + struct SwsContext *swsc; + AVFrame *dst; + AVFrame *tmp; +}; + +struct ffs *ffs_alloc(char *path, int video) +{ + struct ffs *ffs; + int codec_type = video ? CODEC_TYPE_VIDEO : CODEC_TYPE_AUDIO; + int i; + ffs = malloc(sizeof(*ffs)); + memset(ffs, 0, sizeof(*ffs)); + ffs->si = -1; + if (av_open_input_file(&ffs->fc, path, NULL, 0, NULL)) + goto failed; + if (av_find_stream_info(ffs->fc) < 0) + goto failed; + for (i = 0; i < ffs->fc->nb_streams; i++) + if (ffs->fc->streams[i]->codec->codec_type == codec_type) + ffs->si = i; + if (ffs->si == -1) + goto failed; + ffs->cc = ffs->fc->streams[ffs->si]->codec; + avcodec_open(ffs->cc, avcodec_find_decoder(ffs->cc->codec_id)); + return ffs; +failed: + ffs_free(ffs); + return NULL; +} + +void ffs_free(struct ffs *ffs) +{ + if (ffs->swsc) + sws_freeContext(ffs->swsc); + if (ffs->dst) + av_free(ffs->dst); + if (ffs->tmp) + av_free(ffs->tmp); + if (ffs->cc) + avcodec_close(ffs->cc); + if (ffs->fc) + av_close_input_file(ffs->fc); + free(ffs); +} + +static AVPacket *ffs_pkt(struct ffs *ffs) +{ + AVPacket *pkt = &ffs->pkt; + while (av_read_frame(ffs->fc, pkt) >= 0) { + ffs->allseq++; + if (pkt->stream_index == ffs->si) { + ffs->seq++; + return pkt; + } + av_free_packet(pkt); + } + return NULL; +} + +static long ts_ms(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +static void wait(long ts, int vdelay) +{ + int nts = ts_ms(); + if (ts + vdelay > nts) + usleep((ts + vdelay - nts) * 1000); +} + +void ffs_wait(struct ffs *ffs) +{ + long ts = ts_ms(); + if (ts && ffs->ts) { + AVRational *r = &ffs->fc->streams[ffs->si]->time_base; + int vdelay = 1000 * r->num / r->den; + wait(ffs->ts, vdelay); + } + ffs->ts = ts_ms(); +} + +long ffs_pos(struct ffs *ffs, int diff) +{ + return (ffs->si << 28) | (ffs->seq + diff); +} + +void ffs_seek(struct ffs *ffs, long pos, int perframe) +{ + long idx = pos >> 28; + long seq = pos & 0x0fffffff; + av_seek_frame(ffs->fc, idx, seq * perframe, + perframe == 1 ? AVSEEK_FLAG_FRAME : 0); + ffs->seq = seq; + ffs->allseq = 0; + ffs->ts = 0; +} + +long ffs_seq(struct ffs *ffs, int all) +{ + return all ? ffs->allseq : ffs->seq; +} + +void ffs_vinfo(struct ffs *ffs, int *w, int *h) +{ + *h = ffs->cc->height; + *w = ffs->cc->width; +} + +void ffs_ainfo(struct ffs *ffs, int *rate, int *bps, int *ch) +{ + *rate = ffs->cc->sample_rate; + *ch = ffs->cc->channels; + *bps = 16; +} + +int ffs_vdec(struct ffs *ffs, char **buf) +{ + AVCodecContext *vcc = ffs->cc; + AVPacket *pkt = ffs_pkt(ffs); + int fine = 0; + if (!pkt) + return -1; + avcodec_decode_video2(vcc, ffs->tmp, &fine, pkt); + av_free_packet(pkt); + if (fine && buf) { + sws_scale(ffs->swsc, ffs->tmp->data, ffs->tmp->linesize, + 0, vcc->height, ffs->dst->data, ffs->dst->linesize); + *buf = (void *) ffs->dst->data[0]; + return ffs->dst->linesize[0]; + } + return 0; +} + +int ffs_adec(struct ffs *ffs, char *buf, int blen) +{ + int rdec = 0; + AVPacket tmppkt; + AVPacket *pkt = ffs_pkt(ffs); + if (!pkt) + return -1; + tmppkt.size = pkt->size; + tmppkt.data = pkt->data; + while (tmppkt.size > 0) { + int size = blen - rdec; + int len = avcodec_decode_audio3(ffs->cc, (int16_t *) (buf + rdec), + &size, &tmppkt); + if (len < 0) + break; + tmppkt.size -= len; + tmppkt.data += len; + if (size > 0) + rdec += size; + } + av_free_packet(pkt); + return rdec; +} + +void ffs_vsetup(struct ffs *ffs, float zoom, int pixfmt) +{ + int h = ffs->cc->height; + int w = ffs->cc->width; + int fmt = ffs->cc->pix_fmt; + uint8_t *buf = NULL; + int n; + ffs->swsc = sws_getContext(w, h, fmt, w * zoom, h * zoom, + pixfmt, SWS_FAST_BILINEAR | SWS_CPU_CAPS_MMX2, + NULL, NULL, NULL); + ffs->dst = avcodec_alloc_frame(); + ffs->tmp = avcodec_alloc_frame(); + n = avpicture_get_size(pixfmt, w * zoom, h * zoom); + buf = av_malloc(n * sizeof(uint8_t)); + avpicture_fill((AVPicture *) ffs->dst, buf, pixfmt, w * zoom, h * zoom); +} + +void ffs_globinit(void) +{ + av_register_all(); +} diff --git a/ffs.h b/ffs.h new file mode 100644 index 0000000..bf3c95c --- /dev/null +++ b/ffs.h @@ -0,0 +1,19 @@ +void ffs_globinit(void); + +/* ffmpeg stream */ +struct ffs *ffs_alloc(char *path, int video); +void ffs_free(struct ffs *ffs); + +long ffs_pos(struct ffs *ffs, int diff); +void ffs_seek(struct ffs *ffs, long pos, int perframe); +long ffs_seq(struct ffs *ffs, int all); +void ffs_wait(struct ffs *ffs); + +/* audio */ +void ffs_ainfo(struct ffs *ffs, int *rate, int *bps, int *ch); +int ffs_adec(struct ffs *ffs, char *buf, int blen); + +/* video */ +void ffs_vsetup(struct ffs *ffs, float zoom, int pix_fmt); +void ffs_vinfo(struct ffs *ffs, int *w, int *h); +int ffs_vdec(struct ffs *ffs, char **buf); -- 2.11.4.GIT