Skip to content

FFMpeg: clean and split shared C++ fixes before Android integration - #101

Open
defisym wants to merge 12 commits into
masterfrom
codex/ffmpeg-android-jni
Open

defisym wants to merge 12 commits into
masterfrom
codex/ffmpeg-android-jni

Conversation

@defisym

@defisym defisym commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Modifying existing stuff

Port FFMpeg and SDL_Mixer to Android through JNI while reusing the existing C++ playback/adaptor architecture and master's shared Encryption implementation. Clean up dependencies first, then review each shared-core problem separately; platform integration comes last.

The base remains master 36812170 (portable encryption, #91). Already-merged encryption commits are not repeated. Commit author/committer identities remain associated with defisym.

Review order

Author review checkpoint — 2026-11-04: Items 1–7, through 4685fd99 (Send First/read/error states), have been reviewed. Item 8, 6dab60eb (audio pipeline), remains unreviewed. Item 9, 1895bc01 (clocks/controls), is currently being discussed; it and the following items are not marked approved. Resume substantive review at the audio-pipeline commit. Passing automated tests does not change this checkpoint.

Order Commit / intent Scope
1 d8851eda — Cleanup Use split shared helpers; drop the stale probe comment. No duplicate utility headers.
2 808a2bbd — Portable compilation Standard exception/types, qualified Lock references and Windows include guards.
3 058e5d39 — Memory view and AVIO ownership Borrow media data through MemView; manage AVIO buffers independently, use native probing, delegate path constructors and clean up failed initialization.
4 9788a907 — Software conversion Size output from actual frames and report actual stride; preserve default BGRA/Windows alignment.
5 393def8c — Fractional seek and timestamps Keep subsecond targets and source-relative best-effort frame timestamps. Cache the source origin once after stream discovery; read timelineOriginSeconds directly without a forwarding getter.
6 9761ce96 — PCM copy bounds One-line mix-length fix plus a regression that fails before it and passes after it.
7 4685fd99 — Send First and read/error states Preserve the original branches, queue-wait early return, packet argument and unref placement. Handle EOF, temporary reads and real errors distinctly; reset seek state.
8 6dab60eb — Audio pipeline Keep receive-frame → submit-filter → collect-output order and early returns; append available PCM, drain EOF tails, resize storage and reset DSP/preroll on seek.
9 1895bc01 — Playback clocks and controls Monotonic/submitted-audio clock, delayed starts, pause/tempo continuity and seek callback reentry.
10 ca3aa1eb — Shared seek/preview policy Move Windows wrapper policy into the core; unclocked preview, still images, completion and frame callback reentry.
11 8d6b088c — Generic hardware lifecycle Extend the existing adaptor contract for context ownership/selection; keep explicit overrides and software fallback.
12 077893c2 — Android integration — last MediaCodec/GLES adaptors, JNI/Java, shared Mixer X, file/storage ownership, SDK deployment, packaging, crypto API compatibility, platform fixtures and docs.

The first eleven commits contain the C++ core and its focused tests/review notes. No Java, Android adaptor implementation or publishing hook appears in those commits. The final Android commit does not modify FFMpeg.h; its shared playback changes are entirely visible in the preceding problem-specific commits.

Cleanup and shared behavior

  • FFMpegUtilities.h and SDLAudioDefinition.h are removed and never introduced anywhere in the rewritten history. FFMpeg directly includes the existing ArithmeticUtilities, StringUtilities and StringConversion helpers. SDLUtilities.h and its buffer constant remain exactly as on master; the unused FFMpeg alias disappears with the audio-pipeline fix.
  • MemBuf is replaced by a non-owning MemView: it stores a const source pointer and cursor, without allocating/freeing media or IO buffers. FFMpeg separately allocates the AVIO working buffer and frees its current pointer after FFmpeg probing may replace it. Memory playback/seek continue using native avformat probing. The UTF-8 constructor delegates to the existing wide-path constructor through the shared conversion helper.
  • Normal initialized players always have a polymorphic adaptor, including software playback. The destructor guard exists for delegated-constructor failure before the factory is reached (missing input, failed probe/stream discovery or codec initialization); it is not a nullable-adaptor playback policy.
  • Keep the original Send First control flow, including bRespond, immediate QUEUE_WAITING return, pending-packet retry, pInputPacket passed to receive wrappers, and packet unref placement. The decoder function's only semantic change against the original is using its already computed nullable input pointer for the EOF send. Source EAGAIN defers the video tick, completion uses native AVERROR_EOF, and nonnegative queue waiting remains distinct from EIO/EPERM. The FFMpeg.h diff in this commit is reduced to 10 added / 4 removed lines.
  • The decoder commit retains the original audio timestamp code and packet argument contract. Frame-derived audio timestamp handling stays in the following audio-pipeline commit, where its position before filter submission belongs.
  • The audio-pipeline commit retains the original receive-first wrapper structure, KEEP_REF, filter-output loop and early codec/error returns. All available filtered blocks append to the existing audio_buf before returning; codec EOF closes the filter once and drains filter/resampler tails. No separate output queue or pull-first scheduler is introduced. First-frame timing remains anchored before filter submission because filters may buffer input before producing output.
  • In the clocks commit, goto_videoPosition still locks the main context, flushes video/audio queues and the audio decoder, forwards to the target frame, then resets audio timing. Only the external display callback is deferred until after unlocking so synchronized controls can reenter. The image protections are retained: invalid frames do not invoke the copy callback, and single-image no-seek/no-revert policy is preserved in the following shared-policy commit.
  • At review request, the added forwardFrame receive-EAGAIN av_packet_unref is withdrawn. The existing seek-loop packet-release behavior is retained. Its send-EAGAIN retry issue is not claimed resolved by the separate queued-playback decoder fix.
  • Android conversion/build packaging uses the existing DeLib::Helpers module and FTW's pinned iconv dependency, including matching source/notices. There is no second string-conversion implementation or separately selected iconv version.
  • Decoder/filter/resampler drain, PCM bounds, seek timestamps, software buffer sizing, clocks and hardware fallback are shared behavior changes affecting Windows too. They are isolated for review rather than described as Android-only compile fixes. Default BGRA, Windows row alignment, state-only pause and the existing video-only fluent-loop policy are retained. Converted frame storage is named pConvertedFrameBuffer to cover the requested pixel formats; the rename is folded into the software-conversion commit.
  • Shared regressions now live under Extensions/FFMpeg/tests, independently of JNI/Android; the Android runner calls those same sources. The review ledger is in tests/README.md.

Audio review corrections — 2026-11-05

Folded into 6dab60eb; the first seven reviewed commits through 4685fd99 are unchanged. The later four commits are replayed, and the audio commit remains awaiting author review.

  • Restore forwardFrame's frame-grabbing behavior: discard encountered audio packets; do not enqueue them into the main playback queue.
  • Keep the default tempo valid. Create an absent initial graph and explicitly rebuild on seek, without assigning atempo = -1.
  • Treat byte seek offsets as bytes, disable time-based preroll trimming for those seeks, and anchor synchronization to the first valid decoded PTS. Independent preview contexts cannot consume that pending playback anchor.
  • Normalize the negative-flags avformat_seek_file convention before interpreting byte versus time units.
  • Preserve the requested fractional tempo in the filter expression, so the DSP and sample clock use the same value.
  • Keep receive → submit → collect ordering, EOF filter/resampler draining, PCM capacity management and seek resets. Midstream timestamp-gap handling remains a separate follow-up; this correction does not claim full discontinuity support.

Run Extensions/FFMpeg/tests/run_audio_seek.py with the same four FFmpeg/SDL include/library options as the existing shared runner. It generates real MP4/MPEG-TS fixtures and tests byte/time seeks, EOF restart, default graph construction, grab queue isolation and fractional tempo. Optional --baseline-include accepts a directory containing the original c63af8fd header for negative checks.

The amended audio stage and all four later stages pass these ASan/UBSan tests. The original audio commit fails all four targeted negative checks (seek units, byte-seek PCM, grab queue isolation and tempo). The final existing PCMFragmentTest, DecoderProtocolTest and SharedCoreTest also pass. Tests used host FFmpeg 7.1.5 and SDL2; no Windows/Android build or device playback was performed for this correction. Across the audio-review corrections, changes relative to the original d2a04e75 head are limited to FFMpeg.h and four audio regression files.

The follow-up review is split by responsibility:

  • Audio commit 6dab60eb: distinguish sample-clock startup from a valid timestamp anchor. After missing initial audio PTS, use the first later valid timestamp and account for the preceding input duration; reset this state on seek. The new late-pts test fails on the previous 5d903e6f revision and passes after the correction.
  • Clocks commit 1895bc01: estimate playback from submitted PCM, clamp to the actual submitted span (including partial/empty callbacks), and snapshot the callback tempo so changing speed cannot retime already submitted PCM. Pause/resume preserves the remaining submitted span.
  • run_audio_clock.py takes the same four include/library options as the other host runners. Its deterministic clock tests cover 0.25x/1x/1.23x/4x, 4/512/4096-byte callbacks, EOF tails, empty callbacks and tempo/pause continuity. Optional --baseline-include confirms the previous clocks revision fails the EOF-bound and tempo-continuity cases. The test compiler exposes the internal estimator with -fno-access-control; production headers gain no test hooks.
  • The refined audio tests pass at the audio commit and all four later stages. The clock tests pass at the clocks commit and all three later stages; the final PCM, decoder-protocol and shared-core regressions also pass ASan/UBSan. Hardware/device latency is not measured or compensated by this software-only estimate. Windows/Android build and physical playback remain untested for these corrections.

Platform boundaries and dependencies

Android compiles the original FFMpeg class and established adaptor factories. MediaCodec Surface output and GLES copies use platform adaptors beside D3D. Java retains actual Fusion runtime, renderer, storage and file access. Portable playback state, scheduling, queues, seek and clocks remain in C++.

Both plugins use one SDL Mixer X device: FFmpeg's original PCM callback supplies independent looping-chunk/effect channels while SDL_Mixer retains music streams. FFmpeg tempo uses the shared libavfilter chain.

FFmpeg Android deployment reads Windows' FFmpegVersion.cmake; crypto deployment reads EncryptVersion.cmake and its pinned OpenSSL/vcpkg selection. Android SDK version/source/API/ABI checks reject stale inputs. Both exports ship the same shared audio/crypto runtime and matching bindings/source payloads.

MediaCrypto JNI calls the original Encryption C API, explicitly selecting legacy Win32 seed width for password compatibility. File resolution/materialization/cache leases remain in native Fusion application storage. The FileSource compatibility branch is restricted to Android 32-bit API 21–23; other platform/API paths remain unchanged. The standalone Encrypter Fusion extension is still deferred.

Validation for this cleanup and history rewrite

  • Independently compiled and ran all ten portable C++ prefixes, from the portability commit through the generic hardware-lifecycle commit, with host FFmpeg 7.1.5/SDL2. Checks grow with each available API and cover first frame/stride, fractional seek, delayed-frame EOF, audio tail, pause and callback reentry.
  • PCM fragmentation regression demonstrably fails immediately before the one-line mix-length fix and passes immediately afterward; an all-silence result is rejected.
  • Final standalone C++ regressions passed ASan/UBSan: frame/drain counts, filter/resampler audio tail, fractional seek/rewind/preview, Unicode paths, delayed-audio silence/clock, frame and seek callbacks reentering controls, state-only pause, borrowed memory read/seek/size/EOF, full memory A/V playback to EOF, an independent memory-preview context, caller-owned data preservation after player destruction, and invalid-open cleanup. LeakSanitizer is disabled in the managed ptrace environment.
  • The packaged core source/header closure compiled with CMake using the existing DeLib helper module. The pinned four-ABI iconv files and corresponding source hash were verified through FTW's existing validator.
  • Shared Windows/Android FFmpeg version and stale source/version/ABI/missing-library rejection checks passed. Current Windows object helpers are unchanged from the reviewed revision; all 56 helper regressions passed.
  • The minimized Send First revision reran all five affected core stages and independently ran the decoder commit's protocol regression. The final shared suite passed ASan/UBSan, including real FFmpeg-call wrappers checking Send First ordering, queue-wait early return without codec calls, source EAGAIN recovery, and IO EIO/codec EPERM propagation without EOF/waiting collisions. Existing video EOF, DSP tail, delayed-audio, seek and memory-source checks also passed.
  • The audio-order review reran the final shared ASan/UBSan suite and independently compiled/ran the four affected C++ prefixes. Focused 0.25x, 0.5x, 2x and 4x runs reached audio EOF with expected sample totals and identical full PCM for large versus 4-byte callbacks. Only decode_apacket changes in the final tree; the correction is folded into the audio-pipeline commit, preserving the first seven commits and keeping Android last.
  • Verified commit boundaries/authorship, exact cleaned final-tree equivalence and git diff --check. The later format-neutral buffer rename and removal of the redundant timeline-origin getter passed syntax-only C++ compilation. The initialization-time timeline-origin cache passed the shared ASan/UBSan regressions and a before/after seek comparison on a video starting at 7 seconds (relative 1500 ms seeks map to source 8.5 seconds in both versions).

The current core prefixes from the changed ownership commit onward, the expanded ASan/UBSan regressions and the packaged-core CMake build were rerun after the MemView review changes. A focused negative check also confirms that removing the destructor adaptor guard crashes on missing-input construction.

Earlier integration validation is recorded in the Android READMEs: four-ABI native/crypto builds, JNI reference/ACE/lifecycle fixtures, encryption interoperability and the independent Android playback probe. Those results were obtained before this cleanup. This rewrite has not rerun a complete Android export/NDK build, Windows MSBuild or physical-device playback; the new common-helper/iconv linkage still needs those integration builds.

Open review finding: single-image preview

A host comparison before the clocks commit, at the clocks commit, and at current HEAD found the same PNG preview limitation with automatic/2-thread decoding: forwardFrame receives EAGAIN on its single packet, then reaches demux EOF without draining delayed codec output, producing no first-frame callback. This predates the clocks commit and remains unresolved; it is not a deletion of the queue/timing safeguards. Single-thread PNG and JPEG at 1/2/automatic threads render one valid frame without a black replacement; current shared-policy repeated seek/playback calls preserve it. These are host software-decoding ASan/UBSan checks, not Windows/Fusion/device-renderer validation. Track codec EOF draining in forwardFrame separately without reopening already-reviewed core commits.

Build and user testing

See the combined build/file/encryption guide and each extension's Android/README.md. Rebuild/install both ZIPs from the same revision and shared SDK caches.

Opening, seeking, video decoding and materialization remain synchronous on the Fusion calling thread. Native Windows pointer/cSurface expressions retain their documented Android empty/0 behavior.

@defisym defisym changed the title FFMpeg: add Android JNI playback and native rendering adapters Android: port FFMpeg and SDL_Mixer with shared Mixer X audio Oct 31, 2026
@defisym defisym changed the title Android: port FFMpeg and SDL_Mixer with shared Mixer X audio Android: port FFMpeg and SDL_Mixer with shared C++ core and native adaptors Oct 31, 2026
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from 5c63498 to 5164836 Compare October 31, 2026 17:03
@defisym defisym changed the title Android: port FFMpeg and SDL_Mixer with shared C++ core and native adaptors Media: Android FFMpeg and SDL_Mixer on the shared C++ core Oct 31, 2026
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from 5164836 to a536208 Compare November 4, 2026 03:36
@defisym defisym changed the title Media: Android FFMpeg and SDL_Mixer on the shared C++ core Android: share C++ playback and encryption cores across FFMpeg and SDL_Mixer Nov 4, 2026
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from a536208 to a739240 Compare November 4, 2026 03:40
Replace the GeneralDefinition umbrella with the existing arithmetic, string and code-page helpers. Remove a dead probe call-site comment.

Keep the original helper contracts; do not introduce FFMpegUtilities or SDLAudioDefinition copies.
Use a standard exception constructor and fixed-width/standard integer types. Guard the D3D include on Windows and qualify Lock template references hidden by member names.

Error flags and Windows public entry points are retained. Validate by compiling and decoding the first frame on a Linux host.
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from a739240 to f97db56 Compare November 4, 2026 04:53
@defisym defisym changed the title Android: share C++ playback and encryption cores across FFMpeg and SDL_Mixer FFMpeg: clean and split shared C++ fixes before Android integration Nov 4, 2026
Replace MemBuf with a non-owning, read-only MemView and allocate the AVIO working buffer independently with av_malloc. Free the current AVIO buffer explicitly, bound memory seeks and answer size queries. Let avformat probe through custom IO and remove the obsolete manual probe helper. Delegate constructors so partial initialization is destructed; keep the destructor adaptor guard because opening/probing/video-stream discovery can fail before the factory runs. Normal initialized players always have an adaptor, including software playback.

Delegate the UTF-8 path overload to the existing wide-path constructor via the shared code-page conversion. Do not change forwardFrame packet unref behavior in this ownership commit. Host first-frame, memory-source playback/seek and failed-open checks pass.
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from f97db56 to d3b1a78 Compare November 4, 2026 05:32
Cache swscale by actual frame properties and allocate/report the output buffer and stride produced by FFmpeg. Expose the output format as an option while preserving the BGRA default and Windows row alignment.

Host software-conversion stride/frame checks pass.

Name the converted frame storage pConvertedFrameBuffer because callers may request BGRA, BGR24 or RGBA output.
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch 2 times, most recently from e1636d7 to d391071 Compare November 4, 2026 06:13
Convert milliseconds using floating-point seconds and normalize decoded best-effort timestamps against the format start time. Avoid rounding a subsecond seek down to zero.

Host fractional seek and frame-position checks pass.

Cache the timeline origin once after avformat_find_stream_info succeeds, then reuse that per-player value for seek and frame-clock conversions.

Read the initialized timelineOriginSeconds member directly in seek, video and audio conversions; remove the redundant getter.
Pass wt_stream_len instead of the remaining whole request to the mixer. Otherwise a request spanning PCM blocks mixes bytes beyond the current block and can mix the following region twice.

Add a real SDL additive-mixing regression comparing a 100000-byte request with four-byte fragments. It fails immediately before this fix and passes afterward.
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch 2 times, most recently from 65e93fe to e35e9e0 Compare November 4, 2026 06:51
Preserve the original Send First branches, queue-wait early return, pending-packet retry and packet unref placement. Send the already computed nullable input pointer at EOF and keep the existing packet argument passed to receive wrappers.

Use AVERROR_EOF for decoder completion and a nonnegative queue waiting state, keeping EIO/EPERM errors distinct. Propagate read failures and defer a source EAGAIN to the next video tick without spinning or reporting playback failure. Reset pending/finish state on seek.

Audio timestamp and filter/resampler changes remain in the audio-pipeline commit; this commit keeps the original wrapper contract and timestamp placement.

Add real FFmpeg-call regressions for Send First ordering, queue-wait early return without codec calls, source EAGAIN recovery, IO EIO and codec EPERM propagation. Shared playback/EOF, audio tails, delayed audio and memory-source regressions pass under ASan/UBSan.
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch 2 times, most recently from 44845e1 to d2a04e7 Compare November 4, 2026 07:27
Comment thread Extensions/_3rdLib/FFMpeg.h Outdated
set_audioTempo(ATEMPO_DEFAULT);
av_channel_layout_default(&targetChannelLayout, TARGET_CHANNEL_NUMBER);
av_channel_layout_default(&targetChannelLayout, TARGET_CHANNEL_NUMBER);
atempo = -1; // force the initial filter graph to be created

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在这里重置-1强制刷新,但是前面为什么赋初始值啊?!

#endif // AUDIO_TEMPO

float atempo = -1;
float atempo = ATEMPO_DEFAULT;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

就这里

Comment thread Extensions/_3rdLib/FFMpeg.h Outdated

//count++;
}
if (pFormatContext == this->pFormatContext

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这是用于快速跳转视频用于抓帧用的,所以音频包都丢掉了啊

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你这里加进去反而影响主解码的队列出问题了

@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from d2a04e7 to eb78c1e Compare November 5, 2026 13:29
Keep receive-frame, submit-filter, then collect-output ordering and KEEP_REF.
Append all available PCM, send filter EOF once, drain the resampler and grow
storage for accumulated samples. Reset DSP and buffered PCM on seek.

Keep forwardFrame's frame-grabbing policy: discard audio packets without
inserting them into the playback queue. Create the initial filter graph when
absent and explicitly request rebuilds on seek, without assigning a sentinel
tempo. Serialize the actual tempo so the filter and sample clock agree.

Distinguish byte offsets from millisecond targets, including the negative-flags
avformat_seek_file convention. Byte seeks disable time-based preroll trimming
and anchor synchronization to the first valid decoded timestamp; independent
preview contexts do not consume that pending main-playback anchor.

Add standalone audio-seek regressions for native byte seeks, time seek units,
default initialization, same-tempo seek after EOF, grab queue isolation and
fractional tempo. The original c63af8f fails four targeted negative checks.
The amended audio stage and all four later stages pass ASan/UBSan, as do the
final existing PCM, decoder-protocol and shared-core regressions.

Distinguish sample-clock startup from a valid timestamp anchor. If initial audio
frames lack PTS, account for their input duration and accept the first later
valid timestamp. Reset both states on seek. A wrapped real-decoder regression
confirms byte-seek anchoring recovers without losing PCM; the previous revision
fails this case.
Use monotonic time and the submitted PCM timeline, including delayed-audio
silence. Keep decoder/filter lookahead out of the playback clock. Clamp
interpolation to the actual submitted source-time span, including partial and
empty callbacks at EOF or starvation, rather than the requested buffer size.

Snapshot each callback's tempo so a later tempo change cannot retime PCM
already submitted to the audio device. Preserve the remaining callback span
across pause/resume. This remains a software estimate; device latency requires
platform playback-position feedback.

Retain set_pause(..., false) as a state-only update. Publish seek callbacks
outside the audio lock so synchronized controls can reenter. Remove obsolete
timer fields.

Add deterministic clock regressions at 0.25x, 1x, 1.23x and 4x with 4/512/4096
byte callbacks, partial/empty EOF callbacks and tempo changes during a callback.
The previous clocks revision fails the EOF-bound and tempo-continuity tests.
All four affected stages and the existing final shared suite pass ASan/UBSan.
Move the existing Windows seek/preview flags and policy into the shared class, then call it from the Windows wrapper. Add unclocked preview, still-image handling and explicit playback/drain queries.

Publish frame and seek callbacks after releasing the audio lock so control calls can reenter. Keep the existing Windows video-only loop policy. Add a standalone shared-core runner and regressions for seek, drain, AVIO, pause, Unicode paths and callback reentry; ASan/UBSan pass.
Add generic decoder-selection and context-lifetime hooks to the existing adaptor interface. Preserve explicit decoder overrides, accept delayed pixel-format discovery and retry software initialization after hardware setup fails.

Keep the D3D implementation and guard its construction until a real device context exists. No Android adaptor is introduced here. The host core prefix compiles and its shared regressions pass; Windows MSBuild/device validation remains separate.
Add MediaCodec/GLES adaptors through the existing factories, thin JNI and Fusion Java bindings, and one shared SDL Mixer X transport for both plugins. Preserve application/file leases through native Fusion storage.

Add platform SDK deployment and publishing using the Windows FFmpeg/OpenSSL version selectors. Use the existing shared conversion CMake interface and pinned iconv source/archives. Crypto calls the original Encryption core, with compatibility guarded only for Android 32-bit API 21-23; standalone Encrypter remains deferred.

Include platform fixtures, reproduction/build notes and the recorded shared-core/recovery policies. The preceding commits contain the portable playback changes, grouped by intent. Host packaged-core CMake, SDK validation and ASan/UBSan shared-core checks pass; a new full Android export and Windows MSBuild remain for integration testing.
@defisym
defisym force-pushed the codex/ffmpeg-android-jni branch from eb78c1e to 077893c Compare November 5, 2026 14:14

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant