/* fas_seek_to_frame */ fas_error_type fas_seek_to_frame (fas_context_ref_type context, int target_index) { fas_error_type fas_error; if ((NULL == context) || (FAS_FALSE == context->is_video_active)) return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT); // printf("seeking to %d (from %d)!\n", target_index, context->current_frame_index); if (target_index == context->current_frame_index) return FAS_SUCCESS; fas_error = fas_seek_to_nearest_key (context, target_index); if (fas_error != FAS_SUCCESS) return private_show_error("error advancing to key frame before seek", fas_error); if (fas_get_frame_index(context) > target_index) return private_show_error("error advancing to key frame before seek (index isn't right)", fas_error); while (fas_get_frame_index(context) < target_index) { if (fas_frame_available(context)) fas_step_forward(context); else return private_show_error("error advancing to request frame (probably out of range)", FAS_SEEK_ERROR); } return FAS_SUCCESS; }
seek_error_type seek_get_nearest_entry_by_time (seek_table_type *table, seek_entry_type *entry, double time, int offset) { /* using offset>0 returns a modified seek_entry that sets the 'time-to-seek' to be $offset keyframes in the past. */ if (NULL == table || NULL == table->array || table->num_entries <= 0) { return private_show_error ("NULL or invalid seek table", seek_bad_argument); } if (NULL == entry) { return private_show_error ("NULL entry buffer (for return)", seek_bad_argument); } if (time < table->array[0].time) return private_show_error ("tried to seek to frame index before first frame", seek_bad_argument); int i; for (i=0; i < table->num_entries; i++) if (table->array[i].time > time) break; i = i-1; if (i<offset) /* target was lower than first element (including offset) */ return private_show_error ("target index out of table range (too small)", seek_bad_argument); *entry = table->array[i]; (*entry).first_packet_dts = table->array[i-offset].first_packet_dts; return seek_no_error; }
/* private_complete_seek_table */ fas_error_type private_complete_seek_table (fas_context_ref_type context) { fas_error_type fas_error; if ((NULL == context) || (FAS_FALSE == context->is_video_active)) return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT); if (context->seek_table.completed) return FAS_SUCCESS; fas_error = fas_seek_to_nearest_key(context, context->seek_table.num_frames + FIRST_FRAME_INDEX - 1); if (FAS_SUCCESS != fas_error) return private_show_error("failed when trying to complete seek table (1) (first frame not labeled keyframe?)", fas_error); while (fas_frame_available(context)) { // printf("%d\n", context->seek_table.num_frames); fas_step_forward(context); } if (!context->seek_table.completed) return private_show_error("failed when trying to complete seek table (2)", FAS_SEEK_ERROR); return FAS_SUCCESS; }
int fas_get_frame_count (fas_context_ref_type context) { int current_frame; int fast; fas_error_type fas_error; fast = fas_get_frame_count_fast(context); if (fast >= 0) return fast; current_frame = fas_get_frame_index(context); fas_error = private_complete_seek_table(context); if (FAS_SUCCESS != fas_error) { private_show_error("failed in get_frame_count trying to complete the seek table", fas_error); return -1; } // seek_show_raw_table(stderr, context->seek_table); fas_error = fas_seek_to_frame(context, current_frame); if (FAS_SUCCESS != fas_error) { private_show_error("failed in get_frame_count when trying to seek back to original location", fas_error); return -1; } fast = fas_get_frame_count_fast(context); if (fast < 0) private_show_warning("get_frame_count failed"); return fast; }
int fas_get_frame_index (fas_context_ref_type context) { if (NULL == context) return private_show_error("NULL context provided for fas_get_frame_index()", FAS_INVALID_ARGUMENT); if (FAS_TRUE != context->is_video_active) return private_show_error("No video is open for fas_get_frame_index()", FAS_INVALID_ARGUMENT); return context->current_frame_index; }
seek_error_type seek_show_table (seek_table_type table) { seek_entry_type *entry; int index; if (NULL == table.array || table.num_entries <= 0) { return private_show_error ("NULL or invalid seek table", seek_bad_argument); } int completed_flag = 0; if (table.completed == seek_true) completed_flag = 1; fprintf (stderr, "--- Seek Table Dump ---\n"); fprintf (stderr, "n_frames: %d n_entries: %d completed: %d\n",table.num_frames, table.num_entries, completed_flag); for (index = 0; index < table.num_entries; index++) { entry = &(table.array[index]); fprintf (stderr, " %04d %lf --> %08lld (%08lld)\n", entry->display_index, entry->time, entry->first_packet_dts, entry->last_packet_dts); } fprintf (stderr, "-----------------------\n"); return seek_no_error; }
/* fas_put_seek_table */ fas_error_type fas_put_seek_table (fas_context_ref_type context, seek_table_type table) { if (NULL == context || FAS_FALSE == context->is_video_active) return private_show_error("null context or inactive video", FAS_INVALID_ARGUMENT); seek_release_table (&context->seek_table); context->seek_table = seek_copy_table(table); return FAS_SUCCESS; }
static seek_error_type private_resize_table (seek_table_type *table, int new_size) { seek_entry_type *new_array = NULL; if (table == NULL || new_size < 0) { return private_show_error ("invalid argument for private_resize_table()", seek_malloc_failed); } new_array = (seek_entry_type *)malloc (sizeof (seek_entry_type) * new_size); if (NULL == new_array) { return private_show_error ("unable to allocate more space for table", seek_malloc_failed); } memcpy (new_array, table->array, table->allocated_size * sizeof (seek_entry_type)); free (table->array); table->allocated_size = new_size; table->array = new_array; return seek_no_error; }
fas_boolean_type fas_frame_available (fas_context_ref_type context) { if (NULL == context) { private_show_error("NULL context provided for fas_get_frame_index()", FAS_INVALID_ARGUMENT); return FAS_FALSE; } if (!context->is_video_active) return FAS_FALSE; return context->is_frame_available; }
seek_error_type seek_append_table_entry (seek_table_type *table, seek_entry_type entry) { if (NULL == table || NULL == table->array) return private_show_error("null or invalid seek table", seek_bad_argument); if (table->num_entries != 0) if (table->array[table->num_entries - 1].display_index >= entry.display_index) return seek_no_error; if (table->num_entries == table->allocated_size) { seek_error_type error = private_resize_table (table, table->num_entries * 2); if (error != seek_no_error) return private_show_error ("unable to resize seek table", error); } table->array[table->num_entries] = entry; table->num_entries++; return seek_no_error; }
int fas_get_frame_count_fast (fas_context_ref_type context) { if (NULL == context || FAS_FALSE == context->is_video_active) { private_show_error("NULL or invalid context", FAS_INVALID_ARGUMENT); return -1; } if (context->seek_table.completed == seek_true) return context->seek_table.num_frames; return -1; }
fas_error_type private_convert_to_gray8 (fas_context_ref_type ctx) { static struct SwsContext *img_convert_ctx; int w = ctx->codec_context->width; int h = ctx->codec_context->height; img_convert_ctx = sws_getContext(w, h, ctx->codec_context->pix_fmt, w, h, PIX_FMT_GRAY8, SWS_BICUBIC, NULL, NULL, NULL); if(img_convert_ctx == NULL) private_show_error("cannot initialize the conversion context", FAS_DECODING_ERROR); if (ctx->gray8_already_converted) return FAS_SUCCESS; if(sws_scale(img_convert_ctx, ctx->frame_buffer->data, ctx->frame_buffer->linesize, 0, h, ctx->gray8_frame_buffer->data, ctx->gray8_frame_buffer->linesize)) private_show_error("error converting to gray8", FAS_DECODING_ERROR); ctx->gray8_already_converted = FAS_TRUE; return FAS_SUCCESS; }
/* fas_close_video */ fas_error_type fas_close_video (fas_context_ref_type context) { if (NULL == context) return private_show_error("NULL context provided for fas_close_video()", FAS_INVALID_ARGUMENT); if (!(context->is_video_active)) { private_show_warning ("Redundant attempt to close an inactive video"); return FAS_SUCCESS; } if (context->codec_context) if (avcodec_find_decoder (context->codec_context->codec_id)) avcodec_close(context->codec_context); if (context->format_context) av_close_input_file (context->format_context); if (context->rgb_frame_buffer) av_free (context->rgb_frame_buffer); if (context->gray8_frame_buffer) av_free (context->gray8_frame_buffer); if (context->rgb_buffer) av_free(context->rgb_buffer); if (context->gray8_buffer) av_free(context->gray8_buffer); if (context->frame_buffer) av_free (context->frame_buffer); seek_release_table (&(context->seek_table)); context->is_video_active = FAS_FALSE; free (context); return FAS_SUCCESS; }
seek_error_type seek_show_raw_table (FILE* file, seek_table_type table) { seek_entry_type *entry; int index; if (NULL == table.array || table.num_entries <= 0) return private_show_error ("NULL or invalid seek table", seek_bad_argument); int completed_flag = 0; if (table.completed == seek_true) completed_flag = 1; fprintf(file, "%d %d %d\n", table.num_frames, table.num_entries, completed_flag); for (index = 0; index < table.num_entries; index++) { entry = &(table.array[index]); fprintf (file, "%d %lf %lld %lld\n", entry->display_index, entry->time, entry->first_packet_dts, entry->last_packet_dts); } return seek_no_error; }
/* fas_open_video */ fas_error_type fas_open_video (fas_context_ref_type *context_ptr, const char *file_path) { int stream_idx; int numBytes; fas_context_ref_type fas_context; AVCodec *codec; if (NULL == context_ptr) return private_show_error("NULL context pointer provided", FAS_INVALID_ARGUMENT); *context_ptr = NULL; // set returned context to NULL in case of error fas_context = (fas_context_ref_type) malloc(sizeof(fas_context_type)); memset(fas_context, 0, sizeof(fas_context_type)); if (NULL == fas_context) return private_show_error("unable to allocate buffer", FAS_OUT_OF_MEMORY); fas_context->is_video_active = FAS_TRUE; fas_context->is_frame_available = FAS_TRUE; fas_context->current_frame_index = FIRST_FRAME_INDEX - 1; fas_context->current_dts = AV_NOPTS_VALUE; fas_context->previous_dts = AV_NOPTS_VALUE; fas_context->keyframe_packet_dts = AV_NOPTS_VALUE; fas_context->first_dts = AV_NOPTS_VALUE; fas_context->seek_table = seek_init_table(-1); /* default starting size */ if (av_open_input_file(&(fas_context->format_context), file_path, NULL, 0, NULL ) != 0) { fas_close_video(fas_context); return private_show_error("failure to open file", FAS_UNSUPPORTED_FORMAT); } if (av_find_stream_info (fas_context->format_context) < 0) { fas_close_video(fas_context); return private_show_error("could not extract stream information", FAS_UNSUPPORTED_FORMAT); } if (SHOW_WARNING_MESSAGES) av_dump_format(fas_context->format_context, 0, file_path, 0); for (stream_idx = 0; stream_idx < fas_context->format_context->nb_streams; stream_idx++) { if (fas_context->format_context->streams[stream_idx]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { fas_context->stream_idx = stream_idx; fas_context->codec_context = fas_context->format_context->streams[stream_idx]->codec; break; } } if (fas_context->codec_context == 0) { fas_close_video(fas_context); return private_show_error("failure to find a video stream", FAS_UNSUPPORTED_FORMAT); } codec = avcodec_find_decoder(fas_context->codec_context->codec_id); if (!codec) { fas_context->codec_context = 0; fas_close_video(fas_context); return private_show_error("failed to find correct video codec", FAS_UNSUPPORTED_CODEC); } if (avcodec_open(fas_context->codec_context, codec) < 0) { fas_context->codec_context = 0; fas_close_video(fas_context); return private_show_error("failed to open codec", FAS_UNSUPPORTED_CODEC); } fas_context->frame_buffer = avcodec_alloc_frame(); if (fas_context->frame_buffer == NULL) { fas_close_video(fas_context); return private_show_error("failed to allocate frame buffer", FAS_OUT_OF_MEMORY); } fas_context->rgb_frame_buffer = avcodec_alloc_frame(); if (fas_context->rgb_frame_buffer == NULL) { fas_close_video(fas_context); return private_show_error("failed to allocate rgb frame buffer", FAS_OUT_OF_MEMORY); } numBytes = avpicture_get_size(PIX_FMT_RGB24, fas_context->codec_context->width, fas_context->codec_context->height); fas_context->rgb_buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); avpicture_fill((AVPicture *)fas_context->rgb_frame_buffer, fas_context->rgb_buffer, PIX_FMT_RGB24, fas_context->codec_context->width, fas_context->codec_context->height); fas_context->gray8_frame_buffer = avcodec_alloc_frame(); if (fas_context->gray8_frame_buffer == NULL) { fas_close_video(fas_context); return private_show_error("failed to allocate gray8 frame buffer", FAS_OUT_OF_MEMORY); } fas_context->rgb_buffer = 0; fas_context->gray8_buffer = 0; fas_context->rgb_already_converted = FAS_FALSE; fas_context->gray8_already_converted = FAS_FALSE; *context_ptr = fas_context; if (FAS_SUCCESS != fas_step_forward(*context_ptr)) return private_show_error("failure decoding first frame", FAS_NO_MORE_FRAMES); if (!fas_frame_available(*context_ptr)) return private_show_error("couldn't find a first frame (no valid frames in video stream)", FAS_NO_MORE_FRAMES); return FAS_SUCCESS; }
/* fas_step_forward */ fas_error_type fas_step_forward (fas_context_ref_type context) { AVPacket packet; int frameFinished; if ((NULL == context) || (FAS_TRUE != context->is_video_active)) { return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT); } if (!context->is_frame_available) { private_show_warning ("tried to advance after end of frames"); return FAS_SUCCESS; } context->current_frame_index++; while (FAS_TRUE) { if (av_read_frame(context->format_context, &packet) < 0) { /* finished */ context->is_frame_available = FAS_FALSE; context->seek_table.completed = seek_true; return FAS_SUCCESS; } if (packet.stream_index == context->stream_idx) { context->previous_dts = context->current_dts; context->current_dts = packet.dts; /* seek support: set first_dts */ if (context->first_dts == AV_NOPTS_VALUE) context->first_dts = packet.dts; /* seek support: set key-packet info to previous packet's dts, when possible */ /* note this -1 approach to setting the packet is a workaround for a common failure. setting to 0 would work just incur a huge penalty in videos that needed -1. Might be worth testing. */ if (packet.flags & AV_PKT_FLAG_KEY) { //fprintf(stderr, "Packet: (F:%d %lld %lld)\n", context->current_frame_index, packet.pts, packet.dts); if (context->previous_dts == AV_NOPTS_VALUE) context->keyframe_packet_dts = packet.dts; else context->keyframe_packet_dts = context->previous_dts; } avcodec_decode_video2(context->codec_context, context->frame_buffer, &frameFinished, &packet); if (frameFinished) { /* seek support: (try to) add entry to seek_table */ if (context->frame_buffer->key_frame) { // fprintf(stderr, "Frame : (PXX F%d: %lld %lld)\n", context->current_frame_index, packet.pts, packet.dts); seek_entry_type entry; entry.display_index = context->current_frame_index; entry.first_packet_dts = context->keyframe_packet_dts; entry.last_packet_dts = packet.dts; if (fas_get_frame_index(context) == FIRST_FRAME_INDEX) entry.first_packet_dts = context->first_dts; seek_append_table_entry(&context->seek_table, entry); } if (context->current_frame_index - FIRST_FRAME_INDEX + 1 > context->seek_table.num_frames) context->seek_table.num_frames = context->current_frame_index - FIRST_FRAME_INDEX + 1; break; } } av_free_packet(&packet); } context->rgb_already_converted = FAS_FALSE; context->gray8_already_converted = FAS_FALSE; av_free_packet(&packet); return FAS_SUCCESS; }
fas_error_type fas_get_frame(fas_context_ref_type context, fas_raw_image_type *image_ptr) { int buffer_size; fas_error_type fas_error; int j; unsigned char *from; unsigned char *to; if (NULL == context || FAS_FALSE == context->is_video_active) return private_show_error("null context or inactive video", FAS_INVALID_ARGUMENT); if (NULL == image_ptr) return private_show_error("null image_ptr on get_frame", FAS_INVALID_ARGUMENT); if (!fas_frame_available(context)) return private_show_error("no frame available for extraction", FAS_NO_MORE_FRAMES); memset (image_ptr, 0, sizeof (fas_raw_image_type)); switch (fmt) { case PIX_FMT_RGB24: image_ptr->bytes_per_line = context->codec_context->width * 3; image_ptr->color_space = FAS_RGB24; break; case PIX_FMT_BGR24: image_ptr->bytes_per_line = context->codec_context->width * 3; image_ptr->color_space = FAS_BGR24; break; case PIX_FMT_ARGB: image_ptr->bytes_per_line = context->codec_context->width * 4; image_ptr->color_space = FAS_ARGB32; break; case PIX_FMT_ABGR: image_ptr->bytes_per_line = context->codec_context->width * 4; image_ptr->color_space = FAS_ABGR32; break; case PIX_FMT_YUV420P: image_ptr->bytes_per_line = (context->codec_context->width * 3) >> 1; image_ptr->color_space = FAS_YUV420P; break; case PIX_FMT_YUYV422: image_ptr->bytes_per_line = context->codec_context->width * 2; image_ptr->color_space = FAS_YUYV422; break; case PIX_FMT_UYVY422: image_ptr->bytes_per_line = context->codec_context->width * 2; image_ptr->color_space = FAS_UYVY422; break; case PIX_FMT_YUV422P: image_ptr->bytes_per_line = context->codec_context->width * 2; image_ptr->color_space = FAS_YUV422P; break; case PIX_FMT_YUV444P: image_ptr->bytes_per_line = context->codec_context->width * 3; image_ptr->color_space = FAS_YUV444P; break; } buffer_size = image_ptr->bytes_per_line * context->codec_context->height; image_ptr->data = (unsigned char *)malloc (buffer_size); if (NULL == image_ptr->data) return private_show_error("unable to allocate space for RGB image", FAS_OUT_OF_MEMORY); image_ptr->width = context->codec_context->width; image_ptr->height = context->codec_context->height; fas_error = private_convert_to_rgb(context); for (j=0;j<context->codec_context->height; j++) { from = context->rgb_frame_buffer->data[0] + j*context->rgb_frame_buffer->linesize[0]; to = image_ptr->data + j*image_ptr->bytes_per_line; memcpy(to, from, image_ptr->bytes_per_line); } if (FAS_SUCCESS != fas_error) return private_show_error("unable to convert image to RGB", FAS_FAILURE); return FAS_SUCCESS; }
/* private_seek_to_nearest_key */ fas_error_type private_seek_to_nearest_key (fas_context_ref_type context, int target_index, int offset) { fas_error_type fas_error; seek_entry_type seek_entry; seek_error_type seek_error; int flags = 0; if ((NULL == context) || (FAS_TRUE != context->is_video_active)) return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT); #ifdef _DEBUG printf("HERE: from: %d to: %d offset: %d\n", context->current_frame_index, target_index, offset); #endif seek_error = seek_get_nearest_entry(&(context->seek_table), &seek_entry, target_index, offset); if (seek_error != seek_no_error) return private_show_error("error while searching seek table", FAS_SEEK_ERROR); if (seek_entry.display_index == context->current_frame_index) return FAS_SUCCESS; #ifdef _DEBUG printf("HERE: from: %d to: %d (%d) offset: %d\n", context->current_frame_index, target_index, seek_entry.display_index, offset); printf("trying to seek to %d (%lld->%lld)\n", seek_entry.display_index, seek_entry.first_packet_dts, seek_entry.last_packet_dts); #endif // if something goes terribly wrong, return bad current_frame_index context->current_frame_index = -2; context->is_frame_available = FAS_TRUE; if (seek_entry.first_packet_dts <= context->current_dts) flags = AVSEEK_FLAG_BACKWARD; // printf("av_seek_frame: %lld\n", seek_entry.first_packet_dts); if (av_seek_frame(context->format_context, context->stream_idx, seek_entry.first_packet_dts, flags) < 0) return private_show_error("seek to keyframe failed", FAS_SEEK_ERROR); avcodec_flush_buffers (context->codec_context); fas_error = fas_step_forward (context); if (fas_error != FAS_SUCCESS || !context->is_frame_available) { // something bad has happened, try previous keyframe private_show_warning("processing of seeked keyframe failed, trying previous keyframe"); return private_seek_to_nearest_key(context, target_index, offset + 1); } while (context->current_dts < seek_entry.last_packet_dts) { #ifdef _DEBUG printf("frame-times: current: %lld target: %lld is_key: %d\n", context->current_dts, seek_entry.last_packet_dts, context->frame_buffer->key_frame); #endif fas_error = fas_step_forward(context); if (fas_error != FAS_SUCCESS) return private_show_error("unable to process up to target frame (fas_seek_to_frame)", fas_error); } #ifdef _DEBUG printf("keyframe vitals: %d looking_for: %lld at: %lld\n", seek_entry.display_index, seek_entry.last_packet_dts, context->current_dts); #endif if (context->current_dts != seek_entry.last_packet_dts) { /* seek to last key-frame, but look for this one */ private_show_warning("missed keyframe, trying previous keyframe"); return private_seek_to_nearest_key(context, target_index, offset + 1); } /** * Ideally, we could just check if the frame decoded is of the correct time * stamp, but we need several ugly workarounds: * * 1) Some videos have bad keyframes that don't get decoded properly. In this * cases, we need to go back a keyframe. * * 2) Other times, none of the frames are labeled keyframes. In these cases, * we need to allow seeking to frame 0 even when it's not labeled as a * keyframe. Messy set of conditions. */ if ((!context->frame_buffer->key_frame) && (seek_entry.display_index != 0)) { private_show_warning("found keyframe, but not labeled as keyframe, so trying previous keyframe."); /* seek & look for previous keyframe */ /* REMOVE FROM TABLE? */ return private_seek_to_nearest_key(context, seek_entry.display_index - 1, 0); } context->current_frame_index = seek_entry.display_index; return FAS_SUCCESS; }