diff --git a/Kconfig b/Kconfig index 3a65674d2..903a2dce1 100644 --- a/Kconfig +++ b/Kconfig @@ -1384,6 +1384,13 @@ menu "LVGL configuration" bool "Dump format" depends on LV_USE_FFMPEG default n + config LV_FFMPEG_PLAYER_USE_LV_FS + bool "Use lvgl file path in FFmpeg Player widget" + depends on LV_USE_FFMPEG + default n + help + You won't be able to open URLs after enabling this feature. + Note that FFmpeg image decoder will always use lvgl file system. endmenu menu "Others" diff --git a/docs/details/libs/ffmpeg.rst b/docs/details/libs/ffmpeg.rst index 1bcc3f930..97ee1eb1f 100644 --- a/docs/details/libs/ffmpeg.rst +++ b/docs/details/libs/ffmpeg.rst @@ -33,9 +33,7 @@ Enable :c:macro:`LV_USE_FFMPEG` in ``lv_conf.h``. See the examples below. -:note: FFmpeg extension doesn't use LVGL's file system. You can - simply pass the path to the image or video as usual on your operating - system or platform. +:note: Enable :c:macro:`LV_FFMPEG_PLAYER_USE_LV_FS` in ``lv_conf.h`` if you want to integrate the lvgl file system into FFmpeg. .. _ffmpeg_example: diff --git a/lv_conf_template.h b/lv_conf_template.h index 7f1cdec62..469cf5743 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -932,6 +932,10 @@ #if LV_USE_FFMPEG /** Dump input information to stderr */ #define LV_FFMPEG_DUMP_FORMAT 0 + /** Use lvgl file path in FFmpeg Player widget + * You won't be able to open URLs after enabling this feature. + * Note that FFmpeg image decoder will always use lvgl file system. */ + #define LV_FFMPEG_PLAYER_USE_LV_FS 0 #endif /*================== diff --git a/src/libs/ffmpeg/lv_ffmpeg.c b/src/libs/ffmpeg/lv_ffmpeg.c index 6e19be927..073f056f4 100644 --- a/src/libs/ffmpeg/lv_ffmpeg.c +++ b/src/libs/ffmpeg/lv_ffmpeg.c @@ -38,10 +38,14 @@ #define FRAME_DEF_REFR_PERIOD 33 /*[ms]*/ +#define DECODER_BUFFER_SIZE (8 * 1024) + /********************** * TYPEDEFS **********************/ struct ffmpeg_context_s { + AVIOContext * io_ctx; + lv_fs_file_t lv_file; AVFormatContext * fmt_ctx; AVCodecContext * video_dec_ctx; AVStream * video_stream; @@ -75,12 +79,15 @@ static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_d static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc); static void decoder_close(lv_image_decoder_t * dec, lv_image_decoder_dsc_t * dsc); -static struct ffmpeg_context_s * ffmpeg_open_file(const char * path); +static int ffmpeg_lvfs_read(void * ptr, uint8_t * buf, int buf_size); +static int64_t ffmpeg_lvfs_seek(void * ptr, int64_t pos, int whence); +static AVIOContext * ffmpeg_open_io_context(lv_fs_file_t * file); +static struct ffmpeg_context_s * ffmpeg_open_file(const char * path, bool is_lv_fs_path); static void ffmpeg_close(struct ffmpeg_context_s * ffmpeg_ctx); static void ffmpeg_close_src_ctx(struct ffmpeg_context_s * ffmpeg_ctx); static void ffmpeg_close_dst_ctx(struct ffmpeg_context_s * ffmpeg_ctx); static int ffmpeg_image_allocate(struct ffmpeg_context_s * ffmpeg_ctx); -static int ffmpeg_get_image_header(const char * path, lv_image_header_t * header); +static int ffmpeg_get_image_header(lv_image_decoder_dsc_t * dsc, lv_image_header_t * header); static int ffmpeg_get_frame_refr_period(struct ffmpeg_context_s * ffmpeg_ctx); static uint8_t * ffmpeg_get_image_data(struct ffmpeg_context_s * ffmpeg_ctx); static int ffmpeg_update_next_frame(struct ffmpeg_context_s * ffmpeg_ctx); @@ -128,7 +135,7 @@ void lv_ffmpeg_init(void) int lv_ffmpeg_get_frame_num(const char * path) { int ret = -1; - struct ffmpeg_context_s * ffmpeg_ctx = ffmpeg_open_file(path); + struct ffmpeg_context_s * ffmpeg_ctx = ffmpeg_open_file(path, LV_FFMPEG_PLAYER_USE_LV_FS); if(ffmpeg_ctx) { ret = ffmpeg_ctx->video_stream->nb_frames; @@ -159,7 +166,7 @@ lv_result_t lv_ffmpeg_player_set_src(lv_obj_t * obj, const char * path) lv_timer_pause(player->timer); - player->ffmpeg_ctx = ffmpeg_open_file(path); + player->ffmpeg_ctx = ffmpeg_open_file(path, LV_FFMPEG_PLAYER_USE_LV_FS); if(!player->ffmpeg_ctx) { LV_LOG_ERROR("ffmpeg file open failed: %s", path); @@ -260,13 +267,10 @@ static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_d LV_UNUSED(decoder); /* Get the source type */ - const void * src = dsc->src; lv_image_src_t src_type = dsc->src_type; if(src_type == LV_IMAGE_SRC_FILE) { - const char * fn = src; - - if(ffmpeg_get_image_header(fn, header) < 0) { + if(ffmpeg_get_image_header(dsc, header) < 0) { LV_LOG_ERROR("ffmpeg can't get image header"); return LV_RESULT_INVALID; } @@ -291,7 +295,7 @@ static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_d if(dsc->src_type == LV_IMAGE_SRC_FILE) { const char * path = dsc->src; - struct ffmpeg_context_s * ffmpeg_ctx = ffmpeg_open_file(path); + struct ffmpeg_context_s * ffmpeg_ctx = ffmpeg_open_file(path, true); if(ffmpeg_ctx == NULL) { return LV_RESULT_INVALID; @@ -564,18 +568,33 @@ static int ffmpeg_open_codec_context(int * stream_idx, return 0; } -static int ffmpeg_get_image_header(const char * filepath, +static int ffmpeg_get_image_header(lv_image_decoder_dsc_t * dsc, lv_image_header_t * header) { int ret = -1; AVFormatContext * fmt_ctx = NULL; AVCodecContext * video_dec_ctx = NULL; + AVIOContext * io_ctx = NULL; int video_stream_idx; + io_ctx = ffmpeg_open_io_context(&dsc->file); + if(io_ctx == NULL) { + LV_LOG_ERROR("io_ctx malloc failed"); + return ret; + } + + fmt_ctx = avformat_alloc_context(); + if(fmt_ctx == NULL) { + LV_LOG_ERROR("fmt_ctx malloc failed"); + goto failed; + } + fmt_ctx->pb = io_ctx; + fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO; + /* open input file, and allocate format context */ - if(avformat_open_input(&fmt_ctx, filepath, NULL, NULL) < 0) { - LV_LOG_ERROR("Could not open source file %s", filepath); + if(avformat_open_input(&fmt_ctx, (const char *)dsc->src, NULL, NULL) < 0) { + LV_LOG_ERROR("Could not open source file %s", (const char *)dsc->src); goto failed; } @@ -602,7 +621,10 @@ static int ffmpeg_get_image_header(const char * filepath, failed: avcodec_free_context(&video_dec_ctx); avformat_close_input(&fmt_ctx); - + if(io_ctx != NULL) { + av_free(io_ctx->buffer); + av_free(io_ctx); + } return ret; } @@ -658,7 +680,48 @@ static int ffmpeg_update_next_frame(struct ffmpeg_context_s * ffmpeg_ctx) return ret; } -struct ffmpeg_context_s * ffmpeg_open_file(const char * path) +static int ffmpeg_lvfs_read(void * ptr, uint8_t * buf, int buf_size) +{ + lv_fs_file_t * file = ptr; + uint32_t bytesRead = 0; + lv_fs_res_t res = lv_fs_read(file, buf, buf_size, &bytesRead); + if(bytesRead == 0) + return AVERROR_EOF; /* Let FFmpeg know that we have reached eof */ + if(res != LV_FS_RES_OK) + return AVERROR_EOF; + return bytesRead; +} + +static int64_t ffmpeg_lvfs_seek(void * ptr, int64_t pos, int whence) +{ + lv_fs_file_t * file = ptr; + if(whence == SEEK_SET && lv_fs_seek(file, pos, SEEK_SET) == LV_FS_RES_OK) { + return pos; + } + return -1; +} + +static AVIOContext * ffmpeg_open_io_context(lv_fs_file_t * file) +{ + uint8_t * iBuffer = av_malloc(DECODER_BUFFER_SIZE); + if(iBuffer == NULL) { + LV_LOG_ERROR("iBuffer malloc failed"); + return NULL; + } + AVIOContext * pIOCtx = avio_alloc_context(iBuffer, DECODER_BUFFER_SIZE, /* internal Buffer and its size */ + 0, /* bWriteable (1=true,0=false) */ + file, /* user data ; will be passed to our callback functions */ + ffmpeg_lvfs_read, /* Read callback function */ + 0, /* Write callback function */ + ffmpeg_lvfs_seek); /* Seek callback function */ + if(pIOCtx == NULL) { + av_free(iBuffer); + return NULL; + } + return pIOCtx; +} + +static struct ffmpeg_context_s * ffmpeg_open_file(const char * path, bool is_lv_fs_path) { if(path == NULL || lv_strlen(path) == 0) { LV_LOG_ERROR("file path is empty"); @@ -672,6 +735,25 @@ struct ffmpeg_context_s * ffmpeg_open_file(const char * path) goto failed; } + if(is_lv_fs_path) { + lv_fs_open(&(ffmpeg_ctx->lv_file), path, LV_FS_MODE_RD); /* image_decoder_get_info says the file truly exists. */ + + ffmpeg_ctx->io_ctx = ffmpeg_open_io_context(&(ffmpeg_ctx->lv_file)); /* Save the buffer pointer to free it later */ + + if(ffmpeg_ctx->io_ctx == NULL) { + LV_LOG_ERROR("io_ctx malloc failed"); + goto failed; + } + + ffmpeg_ctx->fmt_ctx = avformat_alloc_context(); + if(ffmpeg_ctx->fmt_ctx == NULL) { + LV_LOG_ERROR("fmt_ctx malloc failed"); + goto failed; + } + ffmpeg_ctx->fmt_ctx->pb = ffmpeg_ctx->io_ctx; + ffmpeg_ctx->fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO; + } + /* open input file, and allocate format context */ if(avformat_open_input(&(ffmpeg_ctx->fmt_ctx), path, NULL, NULL) < 0) { @@ -799,6 +881,11 @@ static void ffmpeg_close(struct ffmpeg_context_s * ffmpeg_ctx) sws_freeContext(ffmpeg_ctx->sws_ctx); ffmpeg_close_src_ctx(ffmpeg_ctx); ffmpeg_close_dst_ctx(ffmpeg_ctx); + if(ffmpeg_ctx->io_ctx != NULL) { + av_free(ffmpeg_ctx->io_ctx->buffer); + av_free(ffmpeg_ctx->io_ctx); + lv_fs_close(&(ffmpeg_ctx->lv_file)); + } free(ffmpeg_ctx); LV_LOG_INFO("ffmpeg_ctx closed"); diff --git a/src/libs/ffmpeg/lv_ffmpeg.h b/src/libs/ffmpeg/lv_ffmpeg.h index 1cb86b9d4..753c3fe38 100644 --- a/src/libs/ffmpeg/lv_ffmpeg.h +++ b/src/libs/ffmpeg/lv_ffmpeg.h @@ -59,7 +59,7 @@ int lv_ffmpeg_get_frame_num(const char * path); lv_obj_t * lv_ffmpeg_player_create(lv_obj_t * parent); /** - * Set the path of the file to be played + * Set the path of the file to be played. * @param obj pointer to a ffmpeg_player object * @param path video file path * @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info. diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 5fdad3ebf..f086e023c 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -2994,6 +2994,16 @@ #define LV_FFMPEG_DUMP_FORMAT 0 #endif #endif + /** Use lvgl file path in FFmpeg Player widget + * You won't be able to open URLs after enabling this feature. + * Note that FFmpeg image decoder will always use lvgl file system. */ + #ifndef LV_FFMPEG_PLAYER_USE_LV_FS + #ifdef CONFIG_LV_FFMPEG_PLAYER_USE_LV_FS + #define LV_FFMPEG_PLAYER_USE_LV_FS CONFIG_LV_FFMPEG_PLAYER_USE_LV_FS + #else + #define LV_FFMPEG_PLAYER_USE_LV_FS 0 + #endif + #endif #endif /*==================