fa_handle_t * fa_buffered_open(const char *url, char *errbuf, size_t errsize, int flags, struct fa_open_extra *foe) { buffered_file_t *closeme = NULL; fa_handle_t *fh = NULL; hts_mutex_lock(&buffered_global_mutex); if(parked) { // Flush too old parked files time_t now; time(&now); if(now - parked->bf_park_time > 10) { closeme = parked; parked = NULL; } } if(parked && !strcmp(parked->bf_url, url)) { parked->bf_fpos = 0; fh = (fa_handle_t *)parked; parked = NULL; } hts_mutex_unlock(&buffered_global_mutex); if(closeme != NULL) fab_destroy(closeme); if(fh != NULL) return fh; int mflags = flags; flags &= ~ (FA_BUFFERED_SMALL | FA_BUFFERED_BIG | FA_BUFFERED_NO_PREFETCH); fh = fa_open_ex(url, errbuf, errsize, flags, foe); if(fh == NULL) return NULL; if(!(fh->fh_proto->fap_flags & FAP_ALLOW_CACHE)) return fh; buffered_file_t *bf = calloc(1, sizeof(buffered_file_t)); bf->bf_url = strdup(url); if(!(mflags & FA_BUFFERED_NO_PREFETCH)) bf->bf_min_request = mflags & FA_BUFFERED_BIG ? 256 * 1024 : 64 * 1024; bf->bf_mem_size = 1024 * 1024; bf->bf_flags = flags; bf->bf_src = fh; bf->bf_size = -1; bf->h.fh_proto = &fa_protocol_buffered; #if BF_CHK bf->bf_chk = fa_open_ex(url, NULL, 0, 0, NULL); #endif return &bf->h; }
AVIOContext * fa_libav_open(const char *url, int buf_size, char *errbuf, size_t errlen, int flags, struct prop *stats) { fa_handle_t *fh; if((fh = fa_open_ex(url, errbuf, errlen, flags, stats)) == NULL) return NULL; return fa_libav_reopen(fh, buf_size); }
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; }
static int es_file_open(duk_context *ctx) { char errbuf[512]; es_context_t *ec = es_get(ctx); const char *flagsstr = duk_to_string(ctx, 1); // int mode = duk_to_int(ctx, 2); int flags; if(!strcmp(flagsstr, "r")) { flags = 0; } else if(!strcmp(flagsstr, "w")) { flags = FA_WRITE; } else if(!strcmp(flagsstr, "a")) { flags = FA_WRITE | FA_APPEND; } else { duk_error(ctx, DUK_ERR_ERROR, "Invalid flags '%s' to open", flagsstr); } const char *filename = get_filename(ctx, 0, ec, !!flags); fa_handle_t *fh = fa_open_ex(filename, errbuf, sizeof(errbuf), flags, NULL); if(fh == NULL) duk_error(ctx, DUK_ERR_ERROR, "Unable to open file '%s' -- %s", filename, errbuf); es_fd_t *efd = es_resource_create(ec, &es_resource_fd, 0); efd->efd_path = strdup(filename); efd->efd_fh = fh; es_resource_push(ctx, &efd->super); return 1; }
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; }
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 void screenshot_process(void *task) { pixmap_t *pm = task; if(pm == NULL) { screenshot_response(NULL, "Screenshot not supported on this platform"); return; } TRACE(TRACE_DEBUG, "Screenshot", "Processing image %d x %d", pm->pm_width, pm->pm_height); int codecid = AV_CODEC_ID_PNG; if(screenshot_connection) codecid = AV_CODEC_ID_MJPEG; buf_t *b = screenshot_compress(pm, codecid); pixmap_release(pm); if(b == NULL) { screenshot_response(NULL, "Unable to compress image"); return; } if(!screenshot_connection) { char path[512]; char errbuf[512]; snprintf(path, sizeof(path), "%s/screenshot.png", gconf.cache_path); fa_handle_t *fa = fa_open_ex(path, errbuf, sizeof(errbuf), FA_WRITE, NULL); if(fa == NULL) { TRACE(TRACE_ERROR, "SCREENSHOT", "Unable to open %s -- %s", path, errbuf); buf_release(b); return; } fa_write(fa, buf_data(b), buf_len(b)); fa_close(fa); TRACE(TRACE_INFO, "SCREENSHOT", "Written to %s", path); buf_release(b); return; } buf_t *result = NULL; htsbuf_queue_t hq; htsbuf_queue_init(&hq, 0); htsbuf_append(&hq, "image=", 6); htsbuf_append_and_escape_url_len(&hq, buf_cstr(b), buf_len(b)); char errbuf[256]; int ret = http_req("https://api.imgur.com/3/upload", HTTP_FLAGS(FA_CONTENT_ON_ERROR), HTTP_REQUEST_HEADER("Authorization", "Client-ID 7c79b311d4797ed"), HTTP_RESULT_PTR(&result), HTTP_POSTDATA(&hq, "application/x-www-form-urlencoded"), HTTP_ERRBUF(errbuf, sizeof(errbuf)), NULL); if(ret) { screenshot_response(NULL, errbuf); } else { htsmsg_t *response = htsmsg_json_deserialize(buf_cstr(result)); if(response == NULL) { screenshot_response(NULL, "Unable to parse imgur response"); } else { if(htsmsg_get_u32_or_default(response, "success", 0)) { const char *url = htsmsg_get_str_multi(response, "data", "link", NULL); screenshot_response(url, "No link in imgur response"); } else { const char *msg = htsmsg_get_str_multi(response, "data", "error", NULL); if(msg == NULL) { screenshot_response(NULL, "Unkown imgur error"); } else { snprintf(errbuf, sizeof(errbuf), "Imgur error: %s", msg); screenshot_response(NULL, errbuf); } } htsmsg_release(response); } buf_release(result); } buf_release(b); }
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; }