void fa_libav_close_format(AVFormatContext *fctx) { AVIOContext *avio = fctx->pb; av_close_input_stream(fctx); fa_libav_close(avio); }
metadata_t * fa_probe_metadata(const char *url, char *errbuf, size_t errsize, const char *filename) { AVFormatContext *fctx; fa_handle_t *fh = fa_open_ex(url, errbuf, errsize, FA_BUFFERED_SMALL, NULL); if(fh == NULL) return NULL; metadata_t *md = metadata_create(); #if ENABLE_LIBGME if(gme_probe(md, url, fh)) return md; #endif #if ENABLE_XMP if(xmp_probe(md, url, fh)) return md; #endif fa_seek(fh, 0, SEEK_SET); if(fa_probe_header(md, url, fh, filename)) { fa_close(fh); return md; } AVIOContext *avio = fa_libav_reopen(fh); if((fctx = fa_libav_open_format(avio, url, errbuf, errsize, NULL)) == NULL) { fa_libav_close(avio); metadata_destroy(md); return NULL; } fa_lavf_load_meta(md, fctx, filename); fa_libav_close_format(fctx); return md; }
uint8_t * fa_libav_load_and_close(AVIOContext *avio, size_t *sizep) { size_t r, size = avio_size(avio); uint8_t *mem = malloc(size+1); avio_seek(avio, 0, SEEK_SET); r = avio_read(avio, mem, size); fa_libav_close(avio); if(r != size) { free(mem); return NULL; } if(sizep != NULL) *sizep = size; mem[size] = 0; return mem; }
event_t * be_file_playaudio(const char *url, media_pipe_t *mp, char *errbuf, size_t errlen, int hold, const char *mimetype) { AVFormatContext *fctx; AVCodecContext *ctx; AVPacket pkt; media_format_t *fw; int i, r, si; media_buf_t *mb = NULL; media_queue_t *mq; event_ts_t *ets; int64_t ts, seekbase = 0; media_codec_t *cw; event_t *e; int lost_focus = 0; int registered_play = 0; mp_set_playstatus_by_hold(mp, hold, NULL); fa_handle_t *fh = fa_open_ex(url, errbuf, errlen, FA_BUFFERED_SMALL, NULL); if(fh == NULL) return NULL; // First we need to check for a few other formats #if ENABLE_LIBOPENSPC || ENABLE_LIBGME uint8_t pb[128]; size_t psiz; psiz = fa_read(fh, pb, sizeof(pb)); if(psiz < sizeof(pb)) { fa_close(fh); snprintf(errbuf, errlen, "Fill too small"); return NULL; } #if ENABLE_LIBGME if(*gme_identify_header(pb)) return fa_gme_playfile(mp, fh, errbuf, errlen, hold, url); #endif #if ENABLE_LIBOPENSPC if(!memcmp(pb, "SNES-SPC700 Sound File Data", 27)) return openspc_play(mp, fh, errbuf, errlen); #endif #endif AVIOContext *avio = fa_libav_reopen(fh); if(avio == NULL) { fa_close(fh); return NULL; } if((fctx = fa_libav_open_format(avio, url, errbuf, errlen, mimetype)) == NULL) { fa_libav_close(avio); return NULL; } TRACE(TRACE_DEBUG, "Audio", "Starting playback of %s", url); mp_configure(mp, MP_PLAY_CAPS_SEEK | MP_PLAY_CAPS_PAUSE, MP_BUFFER_SHALLOW); mp->mp_audio.mq_stream = -1; mp->mp_video.mq_stream = -1; fw = media_format_create(fctx); cw = NULL; for(i = 0; i < fctx->nb_streams; i++) { ctx = fctx->streams[i]->codec; if(ctx->codec_type != AVMEDIA_TYPE_AUDIO) continue; cw = media_codec_create(ctx->codec_id, 0, fw, ctx, NULL, mp); mp->mp_audio.mq_stream = i; break; } if(cw == NULL) { media_format_deref(fw); snprintf(errbuf, errlen, "Unable to open codec"); return NULL; } mp_become_primary(mp); mq = &mp->mp_audio; while(1) { /** * Need to fetch a new packet ? */ if(mb == NULL) { mp->mp_eof = 0; r = av_read_frame(fctx, &pkt); if(r == AVERROR(EAGAIN)) continue; if(r == AVERROR_EOF || r == AVERROR(EIO)) { mb = MB_SPECIAL_EOF; mp->mp_eof = 1; continue; } if(r != 0) { char msg[100]; fa_ffmpeg_error_to_txt(r, msg, sizeof(msg)); TRACE(TRACE_ERROR, "Audio", "Playback error: %s", msg); while((e = mp_wait_for_empty_queues(mp)) != NULL) { if(event_is_type(e, EVENT_PLAYQUEUE_JUMP) || event_is_action(e, ACTION_PREV_TRACK) || event_is_action(e, ACTION_NEXT_TRACK) || event_is_action(e, ACTION_STOP)) { mp_flush(mp, 0); break; } event_release(e); } if(e == NULL) e = event_create_type(EVENT_EOF); break; } si = pkt.stream_index; if(si != mp->mp_audio.mq_stream) { av_free_packet(&pkt); continue; } mb = media_buf_alloc_unlocked(mp, pkt.size); mb->mb_data_type = MB_AUDIO; mb->mb_pts = rescale(fctx, pkt.pts, si); mb->mb_dts = rescale(fctx, pkt.dts, si); mb->mb_duration = rescale(fctx, pkt.duration, si); mb->mb_cw = media_codec_ref(cw); /* Move the data pointers from ffmpeg's packet */ mb->mb_stream = pkt.stream_index; memcpy(mb->mb_data, pkt.data, pkt.size); if(mb->mb_pts != AV_NOPTS_VALUE) { if(fctx->start_time == AV_NOPTS_VALUE) mb->mb_time = mb->mb_pts; else mb->mb_time = mb->mb_pts - fctx->start_time; } else mb->mb_time = AV_NOPTS_VALUE; mb->mb_send_pts = 1; av_free_packet(&pkt); } /* * Try to send the buffer. If mb_enqueue() returns something we * catched an event instead of enqueueing the buffer. In this case * 'mb' will be left untouched. */ if(mb == MB_SPECIAL_EOF) { // We have reached EOF, drain queues e = mp_wait_for_empty_queues(mp); if(e == NULL) { e = event_create_type(EVENT_EOF); break; } } else if((e = mb_enqueue_with_events(mp, mq, mb)) == NULL) { mb = NULL; /* Enqueue succeeded */ continue; } if(event_is_type(e, EVENT_PLAYQUEUE_JUMP)) { mp_flush(mp, 0); break; } else if(event_is_type(e, EVENT_CURRENT_PTS)) { ets = (event_ts_t *)e; seekbase = ets->ts; if(registered_play == 0) { if(ets->ts - fctx->start_time > METADB_AUDIO_PLAY_THRESHOLD) { registered_play = 1; metadb_register_play(url, 1, CONTENT_AUDIO); } } } else if(event_is_type(e, EVENT_SEEK)) { ets = (event_ts_t *)e; ts = ets->ts + fctx->start_time; if(ts < fctx->start_time) ts = fctx->start_time; av_seek_frame(fctx, -1, ts, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_FAST_BACKWARD)) { av_seek_frame(fctx, -1, seekbase - 60000000, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_BACKWARD)) { av_seek_frame(fctx, -1, seekbase - 15000000, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_FAST_FORWARD)) { av_seek_frame(fctx, -1, seekbase + 60000000, 0); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_FORWARD)) { av_seek_frame(fctx, -1, seekbase + 15000000, 0); seekflush(mp, &mb); #if 0 } else if(event_is_action(e, ACTION_RESTART_TRACK)) { av_seek_frame(fctx, -1, 0, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); #endif } else if(event_is_action(e, ACTION_PLAYPAUSE) || event_is_action(e, ACTION_PLAY) || event_is_action(e, ACTION_PAUSE)) { hold = action_update_hold_by_event(hold, e); mp_send_cmd_head(mp, mq, hold ? MB_CTRL_PAUSE : MB_CTRL_PLAY); lost_focus = 0; mp_set_playstatus_by_hold(mp, hold, NULL); } else if(event_is_type(e, EVENT_MP_NO_LONGER_PRIMARY)) { hold = 1; lost_focus = 1; mp_send_cmd_head(mp, mq, MB_CTRL_PAUSE); mp_set_playstatus_by_hold(mp, hold, e->e_payload); } else if(event_is_type(e, EVENT_MP_IS_PRIMARY)) { if(lost_focus) { hold = 0; lost_focus = 0; mp_send_cmd_head(mp, mq, MB_CTRL_PLAY); mp_set_playstatus_by_hold(mp, hold, NULL); } } else if(event_is_type(e, EVENT_INTERNAL_PAUSE)) { hold = 1; lost_focus = 0; mp_send_cmd_head(mp, mq, MB_CTRL_PAUSE); mp_set_playstatus_by_hold(mp, hold, e->e_payload); } else if(event_is_action(e, ACTION_PREV_TRACK) || event_is_action(e, ACTION_NEXT_TRACK) || event_is_action(e, ACTION_STOP)) { mp_flush(mp, 0); break; } event_release(e); } if(mb != NULL && mb != MB_SPECIAL_EOF) media_buf_free_unlocked(mp, mb); media_codec_deref(cw); media_format_deref(fw); if(hold) { // If we were paused, release playback again. mp_send_cmd(mp, mq, MB_CTRL_PLAY); mp_set_playstatus_by_hold(mp, 0, NULL); } return e; }
static pixmap_t * fa_image_from_video2(const char *url, const image_meta_t *im, const char *cacheid, char *errbuf, size_t errlen, int sec, time_t mtime, cancellable_t *c) { pixmap_t *pm = NULL; if(ifv_url == NULL || strcmp(url, ifv_url)) { // Need to open int i; AVFormatContext *fctx; fa_handle_t *fh = fa_open_ex(url, errbuf, errlen, FA_BUFFERED_BIG, NULL); if(fh == NULL) return NULL; AVIOContext *avio = fa_libav_reopen(fh, 0); if((fctx = fa_libav_open_format(avio, url, NULL, 0, NULL, 0, 0, 0)) == NULL) { fa_libav_close(avio); snprintf(errbuf, errlen, "Unable to open format"); return NULL; } if(!strcmp(fctx->iformat->name, "avi")) fctx->flags |= AVFMT_FLAG_GENPTS; AVCodecContext *ctx = NULL; for(i = 0; i < fctx->nb_streams; i++) { if(fctx->streams[i]->codec != NULL && fctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { ctx = fctx->streams[i]->codec; break; } } if(ctx == NULL) { fa_libav_close_format(fctx); return NULL; } AVCodec *codec = avcodec_find_decoder(ctx->codec_id); if(codec == NULL) { fa_libav_close_format(fctx); snprintf(errbuf, errlen, "Unable to find codec"); return NULL; } if(avcodec_open2(ctx, codec, NULL) < 0) { fa_libav_close_format(fctx); snprintf(errbuf, errlen, "Unable to open codec"); return NULL; } ifv_close(); ifv_stream = i; ifv_url = strdup(url); ifv_fctx = fctx; ifv_ctx = ctx; } AVPacket pkt; AVFrame *frame = av_frame_alloc(); int got_pic; AVStream *st = ifv_fctx->streams[ifv_stream]; int64_t ts = av_rescale(sec, st->time_base.den, st->time_base.num); if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) { ifv_close(); snprintf(errbuf, errlen, "Unable to seek to %"PRId64, ts); return NULL; } avcodec_flush_buffers(ifv_ctx); #define MAX_FRAME_SCAN 500 int cnt = MAX_FRAME_SCAN; while(1) { int r; r = av_read_frame(ifv_fctx, &pkt); if(r == AVERROR(EAGAIN)) continue; if(r == AVERROR_EOF) break; if(cancellable_is_cancelled(c)) { snprintf(errbuf, errlen, "Cancelled"); av_free_packet(&pkt); break; } if(r != 0) { ifv_close(); break; } if(pkt.stream_index != ifv_stream) { av_free_packet(&pkt); continue; } cnt--; int want_pic = pkt.pts >= ts || cnt <= 0; ifv_ctx->skip_frame = want_pic ? AVDISCARD_DEFAULT : AVDISCARD_NONREF; avcodec_decode_video2(ifv_ctx, frame, &got_pic, &pkt); av_free_packet(&pkt); if(got_pic == 0 || !want_pic) { continue; } int w,h; if(im->im_req_width != -1 && im->im_req_height != -1) { w = im->im_req_width; h = im->im_req_height; } else if(im->im_req_width != -1) { w = im->im_req_width; h = im->im_req_width * ifv_ctx->height / ifv_ctx->width; } else if(im->im_req_height != -1) { w = im->im_req_height * ifv_ctx->width / ifv_ctx->height; h = im->im_req_height; } else { w = im->im_req_width; h = im->im_req_height; } pm = pixmap_create(w, h, PIXMAP_BGR32, 0); if(pm == NULL) { ifv_close(); snprintf(errbuf, errlen, "Out of memory"); av_free(frame); return NULL; } struct SwsContext *sws; sws = sws_getContext(ifv_ctx->width, ifv_ctx->height, ifv_ctx->pix_fmt, w, h, AV_PIX_FMT_BGR32, SWS_BILINEAR, NULL, NULL, NULL); if(sws == NULL) { ifv_close(); snprintf(errbuf, errlen, "Scaling failed"); pixmap_release(pm); av_free(frame); return NULL; } uint8_t *ptr[4] = {0,0,0,0}; int strides[4] = {0,0,0,0}; ptr[0] = pm->pm_pixels; strides[0] = pm->pm_linesize; sws_scale(sws, (const uint8_t **)frame->data, frame->linesize, 0, ifv_ctx->height, ptr, strides); sws_freeContext(sws); write_thumb(ifv_ctx, frame, w, h, cacheid, mtime); break; } av_frame_free(&frame); if(pm == NULL) snprintf(errbuf, errlen, "Frame not found (scanned %d)", MAX_FRAME_SCAN - cnt); avcodec_flush_buffers(ifv_ctx); callout_arm(&thumb_flush_callout, ifv_autoclose, NULL, 5); return pm; }
event_t * be_file_playaudio(const char *url, media_pipe_t *mp, char *errbuf, size_t errlen, int hold) { AVFormatContext *fctx; AVIOContext *avio; AVCodecContext *ctx; AVPacket pkt; media_format_t *fw; int i, r, si; media_buf_t *mb = NULL; media_queue_t *mq; event_ts_t *ets; int64_t ts, pts4seek = 0; media_codec_t *cw; event_t *e; int lost_focus = 0; mp_set_playstatus_by_hold(mp, hold, NULL); if((avio = fa_libav_open(url, 32768, errbuf, errlen)) == NULL) return NULL; // First we need to check for a few other formats #if ENABLE_LIBOPENSPC || ENABLE_LIBGME uint8_t pb[128]; size_t psiz; psiz = avio_read(avio, pb, sizeof(pb)); if(psiz < sizeof(pb)) { fa_libav_close(avio); snprintf(errbuf, errlen, "Fill too small"); return NULL; } #if ENABLE_LIBGME if(*gme_identify_header(pb)) return fa_gme_playfile(mp, avio, errbuf, errlen, hold); #endif #if ENABLE_LIBOPENSPC if(!memcmp(pb, "SNES-SPC700 Sound File Data", 27)) return openspc_play(mp, avio, errbuf, errlen); #endif #endif if((fctx = fa_libav_open_format(avio, url, errbuf, errlen)) == NULL) { fa_libav_close(avio); return NULL; } TRACE(TRACE_DEBUG, "Audio", "Starting playback of %s", url); mp_set_play_caps(mp, MP_PLAY_CAPS_SEEK | MP_PLAY_CAPS_PAUSE); mp->mp_audio.mq_stream = -1; mp->mp_video.mq_stream = -1; fw = media_format_create(fctx); cw = NULL; for(i = 0; i < fctx->nb_streams; i++) { ctx = fctx->streams[i]->codec; if(ctx->codec_type != AVMEDIA_TYPE_AUDIO) continue; cw = media_codec_create(ctx->codec_id, 0, fw, ctx, NULL, mp); mp->mp_audio.mq_stream = i; break; } if(cw == NULL) { media_format_deref(fw); snprintf(errbuf, errlen, "Unable to open codec"); return NULL; } mp_become_primary(mp); mq = &mp->mp_audio; while(1) { /** * Need to fetch a new packet ? */ if(mb == NULL) { if((r = av_read_frame(fctx, &pkt)) < 0) { while((e = mp_wait_for_empty_queues(mp, 0)) != NULL) { if(event_is_type(e, EVENT_PLAYQUEUE_JUMP) || event_is_action(e, ACTION_PREV_TRACK) || event_is_action(e, ACTION_NEXT_TRACK) || event_is_action(e, ACTION_STOP)) { mp_flush(mp, 0); break; } event_release(e); } if(e == NULL) e = event_create_type(EVENT_EOF); break; } si = pkt.stream_index; if(si == mp->mp_audio.mq_stream) { /* Current audio stream */ mb = media_buf_alloc(); mb->mb_data_type = MB_AUDIO; } else { /* Check event queue ? */ av_free_packet(&pkt); continue; } mb->mb_pts = rescale(fctx, pkt.pts, si); mb->mb_dts = rescale(fctx, pkt.dts, si); mb->mb_duration = rescale(fctx, pkt.duration, si); mb->mb_cw = media_codec_ref(cw); /* Move the data pointers from ffmpeg's packet */ mb->mb_stream = pkt.stream_index; av_dup_packet(&pkt); mb->mb_data = pkt.data; pkt.data = NULL; mb->mb_size = pkt.size; pkt.size = 0; if(mb->mb_pts != AV_NOPTS_VALUE) { if(fctx->start_time == AV_NOPTS_VALUE) mb->mb_time = mb->mb_pts; else mb->mb_time = mb->mb_pts - fctx->start_time; pts4seek = mb->mb_time; } else mb->mb_time = AV_NOPTS_VALUE; av_free_packet(&pkt); } /* * Try to send the buffer. If mb_enqueue() returns something we * catched an event instead of enqueueing the buffer. In this case * 'mb' will be left untouched. */ if((e = mb_enqueue_with_events(mp, mq, mb)) == NULL) { mb = NULL; /* Enqueue succeeded */ continue; } if(event_is_type(e, EVENT_PLAYQUEUE_JUMP)) { mp_flush(mp, 0); break; } else if(event_is_type(e, EVENT_SEEK)) { ets = (event_ts_t *)e; ts = ets->pts + fctx->start_time; if(ts < fctx->start_time) ts = fctx->start_time; av_seek_frame(fctx, -1, ts, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_FAST_BACKWARD)) { av_seek_frame(fctx, -1, pts4seek - 60000000, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_BACKWARD)) { av_seek_frame(fctx, -1, pts4seek - 15000000, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_FAST_FORWARD)) { av_seek_frame(fctx, -1, pts4seek + 60000000, 0); seekflush(mp, &mb); } else if(event_is_action(e, ACTION_SEEK_FORWARD)) { av_seek_frame(fctx, -1, pts4seek + 15000000, 0); seekflush(mp, &mb); #if 0 } else if(event_is_action(e, ACTION_RESTART_TRACK)) { av_seek_frame(fctx, -1, 0, AVSEEK_FLAG_BACKWARD); seekflush(mp, &mb); #endif } else if(event_is_action(e, ACTION_PLAYPAUSE) || event_is_action(e, ACTION_PLAY) || event_is_action(e, ACTION_PAUSE)) { hold = action_update_hold_by_event(hold, e); mp_send_cmd_head(mp, mq, hold ? MB_CTRL_PAUSE : MB_CTRL_PLAY); lost_focus = 0; mp_set_playstatus_by_hold(mp, hold, NULL); } else if(event_is_type(e, EVENT_MP_NO_LONGER_PRIMARY)) { hold = 1; lost_focus = 1; mp_send_cmd_head(mp, mq, MB_CTRL_PAUSE); mp_set_playstatus_by_hold(mp, hold, e->e_payload); } else if(event_is_type(e, EVENT_MP_IS_PRIMARY)) { if(lost_focus) { hold = 0; lost_focus = 0; mp_send_cmd_head(mp, mq, MB_CTRL_PLAY); mp_set_playstatus_by_hold(mp, hold, NULL); } } else if(event_is_type(e, EVENT_INTERNAL_PAUSE)) { hold = 1; lost_focus = 0; mp_send_cmd_head(mp, mq, MB_CTRL_PAUSE); mp_set_playstatus_by_hold(mp, hold, e->e_payload); } else if(event_is_action(e, ACTION_PREV_TRACK) || event_is_action(e, ACTION_NEXT_TRACK) || event_is_action(e, ACTION_STOP)) { mp_flush(mp, 0); break; } event_release(e); } if(mb != NULL) media_buf_free(mb); media_codec_deref(cw); media_format_deref(fw); if(hold) { // If we were paused, release playback again. mp_send_cmd(mp, mq, MB_CTRL_PLAY); mp_set_playstatus_by_hold(mp, 0, NULL); } return e; }
static int unpack_audio(const char *url, pcm_sound_t *out) { AVFormatContext *fctx; AVCodecContext *ctx = NULL; char errbuf[256]; fa_handle_t *fh = fa_open_ex(url, errbuf, sizeof(errbuf), 0, NULL); if(fh == NULL) { fail: TRACE(TRACE_ERROR, "audiotest", "Unable to open %s -- %s", url, errbuf); return -1; } AVIOContext *avio = fa_libav_reopen(fh, 0); if((fctx = fa_libav_open_format(avio, url, errbuf, sizeof(errbuf), NULL, 0, -1, -1)) == NULL) { fa_libav_close(avio); goto fail; } int s; for(s = 0; s < fctx->nb_streams; s++) { ctx = fctx->streams[s]->codec; if(ctx->codec_type != AVMEDIA_TYPE_AUDIO) continue; const AVCodec *codec = avcodec_find_decoder(ctx->codec_id); if(codec == NULL) continue; if(avcodec_open2(ctx, codec, NULL) < 0) { TRACE(TRACE_ERROR, "audiotest", "Unable to codec"); continue; } break; } AVFrame *frame = av_frame_alloc(); out->samples = 0; out->data = NULL; while(1) { AVPacket pkt; int r; r = av_read_frame(fctx, &pkt); if(r == AVERROR(EAGAIN)) continue; if(r) break; if(pkt.stream_index == s) { int got_frame; while(pkt.size) { r = avcodec_decode_audio4(ctx, frame, &got_frame, &pkt); if(r < 0) break; if(got_frame) { int ns = frame->nb_samples * 2; out->data = realloc(out->data, sizeof(int16_t) * (out->samples + ns)); const int16_t *src = (const int16_t *)frame->data[0]; for(int i = 0; i < frame->nb_samples; i++) { int16_t v = src[i]; out->data[out->samples + i * 2 + 0] = v; out->data[out->samples + i * 2 + 1] = v; } out->samples += ns; } pkt.data += r; pkt.size -= r; } } av_free_packet(&pkt); } av_frame_free(&frame); avcodec_close(ctx); fa_libav_close_format(fctx); return 0; }
static image_t * fa_image_from_video2(const char *url, const image_meta_t *im, const char *cacheid, char *errbuf, size_t errlen, int sec, time_t mtime, cancellable_t *c) { image_t *img = NULL; if(ifv_url == NULL || strcmp(url, ifv_url)) { // Need to open int i; AVFormatContext *fctx; fa_handle_t *fh = fa_open_ex(url, errbuf, errlen, FA_BUFFERED_BIG, NULL); if(fh == NULL) return NULL; AVIOContext *avio = fa_libav_reopen(fh, 0); if((fctx = fa_libav_open_format(avio, url, NULL, 0, NULL, 0, 0, 0)) == NULL) { fa_libav_close(avio); snprintf(errbuf, errlen, "Unable to open format"); return NULL; } if(!strcmp(fctx->iformat->name, "avi")) fctx->flags |= AVFMT_FLAG_GENPTS; AVCodecContext *ctx = NULL; int vstream = 0; for(i = 0; i < fctx->nb_streams; i++) { AVStream *st = fctx->streams[i]; AVCodecContext *c = st->codec; AVDictionaryEntry *mt; if(c == NULL) continue; switch(c->codec_type) { case AVMEDIA_TYPE_VIDEO: if(ctx == NULL) { vstream = i; ctx = fctx->streams[i]->codec; } break; case AVMEDIA_TYPE_ATTACHMENT: mt = av_dict_get(st->metadata, "mimetype", NULL, AV_DICT_IGNORE_SUFFIX); if(sec == -1 && mt != NULL && (!strcmp(mt->value, "image/jpeg") || !strcmp(mt->value, "image/png"))) { int64_t offset = st->attached_offset; int size = st->attached_size; fa_libav_close_format(fctx); return thumb_from_attachment(url, offset, size, errbuf, errlen, cacheid, mtime); } break; default: break; } } if(ctx == NULL) { fa_libav_close_format(fctx); return NULL; } AVCodec *codec = avcodec_find_decoder(ctx->codec_id); if(codec == NULL) { fa_libav_close_format(fctx); snprintf(errbuf, errlen, "Unable to find codec"); return NULL; } if(avcodec_open2(ctx, codec, NULL) < 0) { fa_libav_close_format(fctx); snprintf(errbuf, errlen, "Unable to open codec"); return NULL; } ifv_close(); ifv_stream = vstream; ifv_url = strdup(url); ifv_fctx = fctx; ifv_ctx = ctx; } AVPacket pkt; AVFrame *frame = av_frame_alloc(); int got_pic; #define MAX_FRAME_SCAN 500 int cnt = MAX_FRAME_SCAN; AVStream *st = ifv_fctx->streams[ifv_stream]; if(sec == -1) { // Automatically try to find a good frame int duration_in_seconds = ifv_fctx->duration / 1000000; sec = MAX(1, duration_in_seconds * 0.05); // 5% of duration sec = MIN(sec, 150); // , buy no longer than 2:30 in sec = MAX(0, MIN(sec, duration_in_seconds - 1)); cnt = 1; } int64_t ts = av_rescale(sec, st->time_base.den, st->time_base.num); int delayed_seek = 0; if(ifv_ctx->codec_id == AV_CODEC_ID_RV40 || ifv_ctx->codec_id == AV_CODEC_ID_RV30) { // Must decode one frame delayed_seek = 1; } else { if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) { ifv_close(); snprintf(errbuf, errlen, "Unable to seek to %"PRId64, ts); return NULL; } } avcodec_flush_buffers(ifv_ctx); while(1) { int r; r = av_read_frame(ifv_fctx, &pkt); if(r == AVERROR(EAGAIN)) continue; if(r == AVERROR_EOF) break; if(cancellable_is_cancelled(c)) { snprintf(errbuf, errlen, "Cancelled"); av_free_packet(&pkt); break; } if(r != 0) { ifv_close(); break; } if(pkt.stream_index != ifv_stream) { av_free_packet(&pkt); continue; } cnt--; int want_pic = pkt.pts >= ts || cnt <= 0; ifv_ctx->skip_frame = want_pic ? AVDISCARD_DEFAULT : AVDISCARD_NONREF; avcodec_decode_video2(ifv_ctx, frame, &got_pic, &pkt); av_free_packet(&pkt); if(delayed_seek) { delayed_seek = 0; if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) { ifv_close(); break; } continue; } if(got_pic == 0 || !want_pic) { continue; } int w,h; if(im->im_req_width != -1 && im->im_req_height != -1) { w = im->im_req_width; h = im->im_req_height; } else if(im->im_req_width != -1) { w = im->im_req_width; h = im->im_req_width * ifv_ctx->height / ifv_ctx->width; } else if(im->im_req_height != -1) { w = im->im_req_height * ifv_ctx->width / ifv_ctx->height; h = im->im_req_height; } else { w = im->im_req_width; h = im->im_req_height; } pixmap_t *pm = pixmap_create(w, h, PIXMAP_BGR32, 0); if(pm == NULL) { ifv_close(); snprintf(errbuf, errlen, "Out of memory"); av_free(frame); return NULL; } struct SwsContext *sws; sws = sws_getContext(ifv_ctx->width, ifv_ctx->height, ifv_ctx->pix_fmt, w, h, AV_PIX_FMT_BGR32, SWS_BILINEAR, NULL, NULL, NULL); if(sws == NULL) { ifv_close(); snprintf(errbuf, errlen, "Scaling failed"); pixmap_release(pm); av_free(frame); return NULL; } uint8_t *ptr[4] = {0,0,0,0}; int strides[4] = {0,0,0,0}; ptr[0] = pm->pm_data; strides[0] = pm->pm_linesize; sws_scale(sws, (const uint8_t **)frame->data, frame->linesize, 0, ifv_ctx->height, ptr, strides); sws_freeContext(sws); write_thumb(ifv_ctx, frame, w, h, cacheid, mtime); img = image_create_from_pixmap(pm); pixmap_release(pm); break; } av_frame_free(&frame); if(img == NULL) snprintf(errbuf, errlen, "Frame not found (scanned %d)", MAX_FRAME_SCAN - cnt); if(ifv_ctx != NULL) { avcodec_flush_buffers(ifv_ctx); callout_arm(&thumb_flush_callout, ifv_autoclose, NULL, 5); } return img; }
static pixmap_t * fa_image_from_video2(const char *url, const image_meta_t *im, const char *cacheid, char *errbuf, size_t errlen, int sec, time_t mtime, fa_load_cb_t *cb, void *opaque) { pixmap_t *pm = NULL; if(ifv_url == NULL || strcmp(url, ifv_url)) { // Need to open int i; AVFormatContext *fctx; fa_handle_t *fh = fa_open_ex(url, errbuf, errlen, FA_BUFFERED_BIG, NULL); if(fh == NULL) return NULL; AVIOContext *avio = fa_libav_reopen(fh); if((fctx = fa_libav_open_format(avio, url, NULL, 0, NULL)) == NULL) { fa_libav_close(avio); snprintf(errbuf, errlen, "Unable to open format"); return NULL; } if(!strcmp(fctx->iformat->name, "avi")) fctx->flags |= AVFMT_FLAG_GENPTS; AVCodecContext *ctx = NULL; for(i = 0; i < fctx->nb_streams; i++) { if(fctx->streams[i]->codec != NULL && fctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { ctx = fctx->streams[i]->codec; break; } } if(ctx == NULL) { fa_libav_close_format(fctx); return NULL; } AVCodec *codec = avcodec_find_decoder(ctx->codec_id); if(codec == NULL) { fa_libav_close_format(fctx); snprintf(errbuf, errlen, "Unable to find codec"); return NULL; } if(avcodec_open(ctx, codec) < 0) { fa_libav_close_format(fctx); snprintf(errbuf, errlen, "Unable to open codec"); return NULL; } ifv_close(); ifv_stream = i; ifv_url = strdup(url); ifv_fctx = fctx; ifv_ctx = ctx; } AVPacket pkt; AVFrame *frame = avcodec_alloc_frame(); int got_pic; AVStream *st = ifv_fctx->streams[ifv_stream]; int64_t ts = av_rescale(sec, st->time_base.den, st->time_base.num); if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) { ifv_close(); snprintf(errbuf, errlen, "Unable to seek to %"PRId64, ts); return NULL; } avcodec_flush_buffers(ifv_ctx); #define MAX_FRAME_SCAN 500 int cnt = MAX_FRAME_SCAN; while(1) { int r; r = av_read_frame(ifv_fctx, &pkt); if(r == AVERROR(EAGAIN)) continue; if(r == AVERROR_EOF) { break; } if(cb != NULL && cb(opaque, 0, 1)) { snprintf(errbuf, errlen, "Aborted"); break; } if(r != 0) { ifv_close(); break; } if(pkt.stream_index != ifv_stream) { av_free_packet(&pkt); continue; } cnt--; int want_pic = pkt.pts >= ts || cnt <= 0; ifv_ctx->skip_frame = want_pic ? AVDISCARD_DEFAULT : AVDISCARD_NONREF; avcodec_decode_video2(ifv_ctx, frame, &got_pic, &pkt); av_free_packet(&pkt); if(got_pic == 0 || !want_pic) { continue; } int w,h; if(im->im_req_width != -1 && im->im_req_height != -1) { w = im->im_req_width; h = im->im_req_height; } else if(im->im_req_width != -1) { w = im->im_req_width; h = im->im_req_width * ifv_ctx->height / ifv_ctx->width; } else if(im->im_req_height != -1) { w = im->im_req_height * ifv_ctx->width / ifv_ctx->height; h = im->im_req_height; } else { w = im->im_req_width; h = im->im_req_height; } pm = pixmap_create(w, h, PIXMAP_RGB24, #ifdef __PPC__ 16 #else 1 #endif ); if(pm == NULL) { ifv_close(); snprintf(errbuf, errlen, "Out of memory"); return NULL; } struct SwsContext *sws; sws = sws_getContext(ifv_ctx->width, ifv_ctx->height, ifv_ctx->pix_fmt, w, h, PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); if(sws == NULL) { ifv_close(); snprintf(errbuf, errlen, "Scaling failed"); pixmap_release(pm); return NULL; } uint8_t *ptr[4] = {0,0,0,0}; int strides[4] = {0,0,0,0}; ptr[0] = pm->pm_pixels; strides[0] = pm->pm_linesize; sws_scale(sws, (const uint8_t **)frame->data, frame->linesize, 0, ifv_ctx->height, ptr, strides); sws_freeContext(sws); if(pngencoder != NULL) { AVFrame *oframe = avcodec_alloc_frame(); memset(&frame, 0, sizeof(frame)); oframe->data[0] = pm->pm_pixels; oframe->linesize[0] = pm->pm_linesize; size_t outputsize = MAX(pm->pm_linesize * h, FF_MIN_BUFFER_SIZE); void *output = malloc(outputsize); pngencoder->width = w; pngencoder->height = h; pngencoder->pix_fmt = PIX_FMT_RGB24; r = avcodec_encode_video(pngencoder, output, outputsize, oframe); if(r > 0) blobcache_put(cacheid, "videothumb", output, r, INT32_MAX, NULL, mtime); free(output); av_free(oframe); } break; } av_free(frame); if(pm == NULL) snprintf(errbuf, errlen, "Frame not found (scanned %d)", MAX_FRAME_SCAN - cnt); return pm; }
static pixmap_t * fa_image_from_video2(const char *url0, const image_meta_t *im, const char *cacheid) { pixmap_t *pm = NULL; char *url = mystrdupa(url0); char *tim = strchr(url, '#'); *tim++ = 0; if(ifv_url == NULL || strcmp(url, ifv_url)) { // Need to open int i; AVFormatContext *fctx; AVIOContext *avio; if((avio = fa_libav_open(url, 65536, NULL, 0, 0)) == NULL) return NULL; if((fctx = fa_libav_open_format(avio, url, NULL, 0, NULL)) == NULL) { fa_libav_close(avio); return NULL; } if(!strcmp(fctx->iformat->name, "avi")) fctx->flags |= AVFMT_FLAG_GENPTS; AVCodecContext *ctx = NULL; for(i = 0; i < fctx->nb_streams; i++) { if(fctx->streams[i]->codec != NULL && fctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { ctx = fctx->streams[i]->codec; break; } } if(ctx == NULL) { fa_libav_close_format(fctx); return NULL; } AVCodec *codec = avcodec_find_decoder(ctx->codec_id); if(codec == NULL) { fa_libav_close_format(fctx); return NULL; } if(avcodec_open(ctx, codec) < 0) { fa_libav_close_format(fctx); return NULL; } ifv_close(); ifv_stream = i; ifv_url = strdup(url); ifv_fctx = fctx; ifv_ctx = ctx; } AVPacket pkt; AVFrame *frame = avcodec_alloc_frame(); int got_pic; int secs = atoi(tim); AVStream *st = ifv_fctx->streams[ifv_stream]; int64_t ts = av_rescale(secs, st->time_base.den, st->time_base.num); if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) { ifv_close(); return NULL; } avcodec_flush_buffers(ifv_ctx); int cnt = 500; while(1) { int r; r = av_read_frame(ifv_fctx, &pkt); if(r == AVERROR(EAGAIN)) continue; if(r == AVERROR_EOF) break; if(r != 0) { ifv_close(); break; } if(pkt.stream_index != ifv_stream) { av_free_packet(&pkt); continue; } cnt--; int want_pic = pkt.pts >= ts || cnt <= 0; ifv_ctx->skip_frame = want_pic ? AVDISCARD_DEFAULT : AVDISCARD_NONREF; avcodec_decode_video2(ifv_ctx, frame, &got_pic, &pkt); if(got_pic == 0 || !want_pic) continue; int w,h; if(im->req_width != -1 && im->req_height != -1) { w = im->req_width; h = im->req_height; } else if(im->req_width != -1) { w = im->req_width; h = im->req_width * ifv_ctx->height / ifv_ctx->width; } else if(im->req_height != -1) { w = im->req_height * ifv_ctx->width / ifv_ctx->height; h = im->req_height; } else { w = im->req_width; h = im->req_height; } pm = pixmap_create(w, h, PIX_FMT_RGB24); struct SwsContext *sws; sws = sws_getContext(ifv_ctx->width, ifv_ctx->height, ifv_ctx->pix_fmt, w, h, PIX_FMT_RGB24, SWS_LANCZOS, NULL, NULL, NULL); if(sws == NULL) { ifv_close(); return NULL; } uint8_t *ptr[4] = {0,0,0,0}; int strides[4] = {0,0,0,0}; ptr[0] = pm->pm_pixels; strides[0] = pm->pm_linesize; sws_scale(sws, (const uint8_t **)frame->data, frame->linesize, 0, ifv_ctx->height, ptr, strides); sws_freeContext(sws); if(pngencoder != NULL) { AVFrame *oframe = avcodec_alloc_frame(); memset(&frame, 0, sizeof(frame)); oframe->data[0] = pm->pm_pixels; oframe->linesize[0] = pm->pm_linesize; size_t outputsize = pm->pm_linesize * h; void *output = malloc(outputsize); pngencoder->width = w; pngencoder->height = h; pngencoder->pix_fmt = PIX_FMT_RGB24; r = avcodec_encode_video(pngencoder, output, outputsize, oframe); if(r > 0) blobcache_put(cacheid, "videothumb", output, outputsize, 86400 * 5); free(output); av_free(oframe); } break; } av_free(frame); return pm; }
pixmap_t * fa_imageloader(const char *url, const struct image_meta *im, const char **vpaths, char *errbuf, size_t errlen) { uint8_t p[16]; int r; enum CodecID codec; int width = -1, height = -1, orientation = 0; AVIOContext *avio; pixmap_t *pm; if(strchr(url, '#')) { pm = fa_image_from_video(url, im); if(pm == NULL) snprintf(errbuf, errlen, "%s: Unable to extract image", url); return pm; } if(!im->want_thumb) return fa_imageloader2(url, vpaths, errbuf, errlen); if((avio = fa_libav_open_vpaths(url, 32768, vpaths)) == NULL) { snprintf(errbuf, errlen, "%s: Unable to open file", url); return NULL; } if(avio_read(avio, p, sizeof(p)) != sizeof(p)) { snprintf(errbuf, errlen, "%s: file too short", url); fa_libav_close(avio); return NULL; } /* Probe format */ if((p[6] == 'J' && p[7] == 'F' && p[8] == 'I' && p[9] == 'F') || (p[6] == 'E' && p[7] == 'x' && p[8] == 'i' && p[9] == 'f')) { jpeginfo_t ji; if(jpeg_info(&ji, jpeginfo_reader, avio, JPEG_INFO_DIMENSIONS | JPEG_INFO_ORIENTATION | (im->want_thumb ? JPEG_INFO_THUMBNAIL : 0), p, sizeof(p), errbuf, errlen)) { fa_libav_close(avio); return NULL; } if(im->want_thumb && ji.ji_thumbnail) { pixmap_t *pm = pixmap_dup(ji.ji_thumbnail); fa_libav_close(avio); jpeg_info_clear(&ji); return pm; } codec = CODEC_ID_MJPEG; width = ji.ji_width; height = ji.ji_height; orientation = ji.ji_orientation; jpeg_info_clear(&ji); } else if(!memcmp(pngsig, p, 8)) { codec = CODEC_ID_PNG; } else if(!memcmp(gif87sig, p, sizeof(gif87sig)) || !memcmp(gif89sig, p, sizeof(gif89sig))) { codec = CODEC_ID_GIF; } else { snprintf(errbuf, errlen, "%s: unknown format", url); fa_libav_close(avio); return NULL; } size_t s = avio_size(avio); if(s < 0) { snprintf(errbuf, errlen, "%s: Can't read from non-seekable file", url); fa_libav_close(avio); return NULL; } pm = pixmap_alloc_coded(NULL, s, codec); if(pm == NULL) { snprintf(errbuf, errlen, "%s: no memory", url); fa_libav_close(avio); return NULL; } pm->pm_width = width; pm->pm_height = height; pm->pm_orientation = orientation; avio_seek(avio, SEEK_SET, 0); r = avio_read(avio, pm->pm_data, pm->pm_size); fa_libav_close(avio); if(r != pm->pm_size) { pixmap_release(pm); snprintf(errbuf, errlen, "%s: read error", url); return NULL; } return pm; }