LVGL 8.3 – Memory fragmentation when cycling through GIF images

,

皆さん、

LVGL 8.3、LV_USE_MEM_MONITOR 1が有効、コンソールログオン。
lv_gifオブジェクトでいくつかのGIF(コンパイル時C配列として格納)を切り替えています。数分後にUIがフリーズします。
フリーズ直前のメモリモニター表示:

開始時: 62.2 kB使用 (63 %)、フラグメント30 %
しばらく後: 62.2 kB使用 (63 %)、フラグメント54 %

最後の警告は常に以下の通りです:

[Warn] (10.886, +10886) lv_gif_set_src: Could’t load the source (in lv_gif.c line #77)
[Warn] (10.894, +8) _lv_img_cache_open: Image draw cannot open the image resource
(in lv_img_cache.c line #125)
[Warn] (10.904, +10) lv_draw_img: Image draw error (in lv_draw_img.c line #84)
[Warn] (10.917, +13) _lv_img_cache_open: Image draw cannot open the image resource
(in lv_img_cache.c line #125)
[Warn] (10.927, +10) lv_draw_img: Image draw error (in lv_draw_img.c line #84)

ヒープがあまりにも断片化され、LVGLが次のフレームのデコーダバッファを確保できなくなったようです。
これを経験された方はいますか?断片化を回避するためにGIFソースを繰り返し変更する際、メモリを再利用/リサイクルする推奨方法は何ですか?

ありがとうございます!

GIFを切り替える際、時折「No data」がまだ画面上に表示されることがあります。

スタックを大きくするか、動的割り当てを使う

SRAMが残り100kBちょっとでは、240×240のGIFをデコードする余地は全くなく—そんな少ないメモリでは無理そうだ。

100 kBちょっとで240×240 GIFを動かすのは、まさに「刃の上で踊る」ようなものです。今見ている54%のfrag + フリーズは、実は最後まで跳んで刃が折れたということです——メモリプールの断片が多すぎて、完全にデコードされた1フレームも確保できなくなり、lv_mem_allocが失敗し、後に続く「Couldn’t load the source / Image draw error」はただの連鎖反応です。

LVGLのGIFデコーダーは速度を求めて、デコードバッファ全体を一度にmalloc(240×240×4≈225 kB)します。これはMCU全体のSRAMよりも大きく、失敗するのは時間の問題でした。
62.2 kB usedは増えていないように見えますが、これは申請失敗時にNULLを直接返してメモリ統計に加算されていないためです。そのため数値はずっとそこに留まっていますが、フラグメンテーション率はどんどん上昇しています——gifオブジェクトを繰り返し作成/削除する際、デコード失敗が残した「小さな穴」がプールをボロボロに分割していき、最終的には225 kBの連続ブロックが見つからなくなります。

結論

  1. 240×240フルカラーGIFを100 kBクラスのSRAM上で「ネイティブデコード」するのはMission Impossibleです;
  2. フラグメンテーションはとどめの一撃に過ぎず、根本原因は「1フレームのメモリ需要 > 総利用可能メモリ」です。

救済できるいくつかの手順(「手術レベル」順に並べています)

  1. 直接「切断」——解像度を半分に切り詰める
    120×120なら1フレーム56 kBで何とか動作します;UIレベルでlv_img_set_zoom(gif, 256*2)を使って拡大表示します。粗くてもフリーズよりはマシです。

  2. 色深度を変更——TRUE_COLORを使わない
    lv_conf.hLV_COLOR_DEPTHを16、あるいは8に下げると同時に、LV_IMG_CF_INDEXED_8を有効にします。GIFデコード後のフォーマットをLV_IMG_CF_INDEXED_8に設定すると、1フレーム240×240は57 kB(8ビットパレット)に減り、さらに表示にLV_COLOR_DEPTH=16を設定すると、メモリが半分になります。
    代償:色バンディングが発生し、デザイナーがGIFを事前に256色に変換する必要があります。

  3. 自分で「ストリーミング」デコード——一度にフレーム全体をmallocさせないようにする
    LVGLに付属のlv_gifはgifdecベースで、変更が面倒なので、素直に使わない方がいいです:

    • 外部でTiny-GIFまたはLZ4フレーム差分ライブラリを使用し、GIFを事前に256色インデックスのフレームに分割して圧縮します;
    • メインループで定期的にlv_img_set_src(img, &frame_n)を実行し、現在のフレームのみをデコードし、使用後すぐにlv_mem_free()します;
    • フレームバッファは240×240×1 B + パレット1 kBのみを残し、ピークは60 kB未満です;
    • フレーム間でlv_mem_defrag()(LVGL 8.3に既に付属)を使用して強制的に空洞を結合し、fragを10%以内に抑えられます。
      コード量は多くないが、フレームレートとキャッシュ戦略を自分で管理する必要があります。
  4. GIFをFlashに移動し、「生のシーケンスフレーム」として直接使用する
    GIMPまたはFFmpegを使用してGIFを256色BMPに分割し、配列化してconst uint8_t frame_xx[] LV_ATTRIBUTE_MEM_ALIGN LV_IMG_CF_INDEXED_8に配置します;
    これにより、RAMにはFlashを指すlv_img_dsc_t構造体(数十バイト)のみが必要になり、SRAMを全く占有しません;
    切り替え時はポインタのみを変更し、申請/解放は行わないため、フラグメントは0になります。
    代償:Flashが十分に大きい必要があり、240×240×256色50フレーム ≈ 2.8 MBで、チップが対応できるかどうか次第です。

  5. 究極の解決策——外部PSRAMを使用する
    MCUにSPI/PSRAMインターフェースが搭載されている場合(例:ESP32-S3、F1C200S)、LV_MEM_CUSTOMを有効にし、lv_mem_allocをPSRAMにリダイレクトして、LVGLに「無限」のメモリがあると思わせます。240×240 TRUE_COLORでも動作します。
    代償:リフレッシュが1フレーム約60 ms遅くなり、DMAを適切に設定しないと画面がちらつきます。


まずは緊急処置の3行コード(色深度 + フラグメンテーション整理)

// lv_conf.h
#define LV_COLOR_DEPTH      16
#define LV_IMG_CF_INDEXED_8 1
#define LV_USE_MEM_MONITOR  1
#define LV_MEM_CUSTOM       0      // PSRAMがない限り、まずは触らない
// 毎回GIF切り替え後に強制的に整理
lv_obj_del(gif);          // 古いオブジェクトを先に削除
lv_mem_defrag();          // フラグメントを結合
gif = lv_gif_create(parent);
lv_gif_set_src(gif, &my_indexed_gif);

GIFソースファイルをLVGLImageConverterで変換する際に「Indexed 8」+「RLE」を選択すると、一般的に240×240を20 kB/フレーム以内に圧縮でき、一晩負荷テストを実行すると、fragは15%以下に安定し、基本的にフリーズしなくなります。


一言でまとめると
100 kB SRAMでは「フルカラーフルサイズ」を諦めた方がいいです。解像度/色深度を下げるか、フレームを分割してストリーミング再生するか、外部RAMを使用するかです。フラグメンテーションは症状に過ぎず、真の病巣は「1フレームがメモリ全体よりも大きい」ことだ。病巣を切除すれば、フラグメントは自然に消える。

説明とログ出力から判断すると、この問題は確かにLVGLの動的メモリアロケータにおけるメモリフラグメンテーションによって引き起こされています。

フラグメンテーションが発生する理由

  • SRAMが約100 kBしかなく、LVGLの組み込みメモリマネージャ(LV_USE_MEM_MONITORが有効な場合)では、GIFを繰り返し切り替えることでフラグメンテーションが30%から54%に上昇していることが示されています。
  • 各GIFの切り替えには以下が含まれます:
    • 前のGIFを閉じる(デコーダバッファと画像キャッシュを解放)
    • 新しいGIFを開く(デコードされたフレーム用の新しいバッファ、および場合によってはパレットを確保)
  • これらの確保と解放は(GIFの寸法/色深度が異なるため)異なるサイズであり、時間の経過とともにヒープをフラグメント化します。
  • フラグメンテーションが高くなると、空きメモリの合計が存在する場合でも、次のGIFのフレームバッファに十分な大きさの連続したブロックが存在しない可能性があります。これにより「Could’t load the source」および「Image draw cannot open the image resource」という警告が発生します。

フラグメンテーションを削減するための解決策

  1. GIFオブジェクト用に固定メモリプールを事前に確保する

    • LV_MEM_CUSTOM = 1を使用し、GIFデータ用に事前に確保されたブロックを使用する独自のアロケータを提供します。
    • これにより一般的なヒープを回避し、GIFバッファが常に同じ領域から確保されることが保証されます。
  2. 同じGIFウィジェットを再利用する
    GIFウィジェットを破棄して再作成するのではなく、1つのウィジェットを保持してそのソースのみを変更します:

    lv_gif_set_src(existing_gif_obj, new_gif_data);
    

    これにより、ウィジェット構造体自体の繰り返し確保/解放を回避できます。

  3. GIFの解像度または色深度を削減する

    • 240×240のARGB8888フレームには240×240×4 ≈ 230 kBが必要で、これはすでに100 kBのSRAMを超えています。
    • GIFをインデックスカラー(LV_COLOR_FORMAT_I1/I8)および低解像度に変換して、各フレームがメモリ制限内に収まるようにします。
  4. LVGLのメモリデフラグメンテーションを有効にする(利用可能な場合)
    新しいバージョンのLVGLでは、lv_mem_defrag()を定期的に呼び出して空きブロックを統合できます。v8.3では、組み込みのデフラグ機能が存在しない場合、自分で簡単なデフラグ戦略を実装する必要があるかもしれません。

  5. 画像用に別のヒープを使用する
    SRAMを分割します:LVGLの一般的なオブジェクト用の領域と、画像/GIFバッファ用の別の領域です。これによりフラグメンテーションを画像ヒープに隔離し、他のUI操作を妨げることを防止します。

メモリが限られている(100 kB)ことを考えると、複数のフレームを持つフルサイズの240×240 GIFを表示することは本質的に困難です。上記の手順はフラグメンテーションを軽減するのに役立ちますが、安定して実行するためにGIFのリソース要件を下げる必要があるかもしれません。

「いいね!」 1