Skip to content

Fix screen mirroring audio: native FDK-AAC decoder, buffer overflow fix, CI packaging - #5

Merged
YimingZhanshen merged 14 commits into
mainfrom
copilot/fix-screen-mirroring-sound
Feb 10, 2026
Merged

YimingZhanshen merged 14 commits into
mainfrom
copilot/fix-screen-mirroring-sound

Conversation

Copilot AI commented Feb 10, 2026

Copy link
Copy Markdown

Screen mirroring had no audio output. Root cause was multi-layered: AAC-ELD frames were wrapped in ADTS/LOAS containers that FFmpeg couldn't parse correctly, the RaopBuffer dequeue had a short overflow causing playback stalls, and the CI workflow was missing required native dependencies.

Native FDK-AAC decoder

  • Added NativeFdkAacEldDecoder using P/Invoke to libfdk-aac-2.dll with TT_MP4_RAW transport, matching the approach in itskenny0/airplayreceiver and UxPlay
  • Feeds raw decrypted AAC-ELD frames directly — no ADTS/LOAS framing needed
  • AudioSpecificConfig f8e85000 (AOT=39, 44100Hz, stereo) matches UxPlay exactly
  • Falls back to FFmpeg subprocess → SharpJaad if native lib unavailable

Buffer overflow fix (audio stuttering)

  • RaopBufferDequeue cast buflen to short, overflowing negative when seq gap > 32767 → dequeue stops entirely, queue grows unbounded
  • Changed to ushort arithmetic to handle full 16-bit sequence number range
  • Set no_resend=true during mirroring — real-time audio should skip missing packets, not wait

Thread safety

  • Separate IBufferedCipher instances per socket handler (CBC mode is stateful)
  • _bufferLock synchronizes RaopBuffer access between concurrent control/data handlers
  • FlushAsync now acquires lock before clearing buffer

Protocol fixes

  • Added SETPEERS RTSP request handler (was silently dropped, no regex match)
  • Fixed plist integer casting: Convert.ToInt32() instead of direct (int) unbox on Int16

CI & documentation

  • Build FDK-AAC from source via MSYS2+CMake in CI, bundle libfdk-aac-2.dll + FFmpeg in artifacts
  • Rewrote README.md, added README_zh.md

Debug logging

Comprehensive [DEBUG-*] prefixed logging throughout the audio pipeline for future diagnostics:
[DEBUG-FDK], [DEBUG-DECRYPT], [DEBUG-DECODE], [DEBUG-QUEUE], [DEBUG-RTSP], [DEBUG-SETUP], [DEBUG-TEARDOWN]

Original prompt

此项目在使用屏幕镜像的时候没有声音,但日志却正常:“Initializing audio output...
Audio device created: 44100Hz, 16-bit, 2 channels
Will call Init() after prebuffering 10 packets
Audio output initialized successfully
Video pipe created: \.\pipe\AirPlayVideo
Connect with: ffplay -f h264 -probesize 32 -analyzeduration 0 -fflags nobuffer -flags low_delay \.\pipe\AirPlayVideo
Video output initialized successfully
Waiting for video player to connect...
IP address 240e:390:3c1:7460::a6d
IP address 240e:390:3c1:7460:fac8:bb16:3565:b33a
IP address 240e:390:3c1:7460:8163:2eae:8d83:9dfb
IP address fe80::126d:dcfa:6b21:6f27%22
IP address 192.168.0.99
IP address 2001:0:14c9:d502:83f:2303:25b7:b7c8
IP address fe80::83f:2303:25b7:b7c8%20
NIC 'WLAN 6'
NIC 'Teredo Tunneling Pseudo-Interface'
Video player connected!
Client connected: 192.168.0.215:52614
PairVerified: True
ET: 32
Client connected: 192.168.0.215:52615
Client connected: 192.168.0.215:52616
Volume set to 1.00 (AirPlay dB: 0.0)
Audio type: 96 mpeg4-generic/44100/2, 96 mode=AAC-eld; constantDuration=480
Initializing recevie audio data from socket..
Initializing recevie audio control from socket..
FFmpeg AAC-ELD decoder started: 44100Hz, 2ch, 16bit, frameLength=480
FFmpeg AAC-ELD decoder started: 44100Hz, 2ch, 16bit, frameLength=480
Video: 300 frames written | Type: P | 1920x1080”,请修复此问题(关于屏幕镜像的音频部分你可以参照 https://github.com/itskenny0/airplayreceiver.git


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 10, 2026 01:33
…ace condition

ADTS format cannot represent AAC-ELD (its 2-bit profile field only supports
AAC-Main/LC/SSR). Using LATM/LOAS wrapping which supports all AAC profiles
including AAC-ELD (AOT 39).

Also added thread-safety lock on decoder initialization to prevent race
condition between concurrent OnRawCSocketAsync and OnRawDSocketAsync handlers
that was causing duplicate FFmpeg processes to be spawned.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
… condition

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix audio output issues during screen mirroring Fix screen mirroring audio: use LATM instead of ADTS for AAC-ELD decoding Feb 10, 2026
Copilot AI requested a review from YimingZhanshen February 10, 2026 01:39
The previous change used `-f latm` as FFmpeg input format, but FFmpeg has
no `latm` demuxer (only a muxer). This caused FFmpeg to exit immediately,
and every subsequent DecodeFrame call returned -1 (HasExited check),
producing the "Decoding error. Decoder: AAC_ELD Code: -1" error spam.

Revert to ADTS wrapping with `-f aac` which keeps FFmpeg alive. While
ADTS headers can only encode AAC-LC profile (2-bit field), FFmpeg's AAC
decoder handles the actual bitstream content. The ADTS header serves as
a framing/sync mechanism.

Also improved stderr handling to filter per-frame warnings and only log
critical errors with case-insensitive matching.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: use LATM instead of ADTS for AAC-ELD decoding Fix FFmpeg AAC-ELD decoder: revert LATM wrapping to ADTS, fix process crash Feb 10, 2026
Copilot AI and others added 2 commits February 10, 2026 09:47
… add exception handling

The AES-CBC cipher is stateful (tracks IV internally). Sharing one instance
between the concurrent control and data socket handlers caused cipher state
corruption, leading to decryption failures and crashes.

Fix: Each handler now creates its own cipher instance. RaopBuffer access is
synchronized with a lock. Try-catch blocks prevent socket errors from
silently killing the handler tasks.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Dequeue PCM data into a batch while holding the lock, then deliver
outside the lock. This prevents the receiver's processing time from
blocking the other socket handler.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix FFmpeg AAC-ELD decoder: revert LATM wrapping to ADTS, fix process crash Fix screen mirroring audio: thread-safe cipher/buffer, prevent silent handler crashes Feb 10, 2026
All log lines prefixed with [DEBUG-*] tags for easy filtering:
- [DEBUG-C] Control socket (port 7002): packet types, counts, exit reason
- [DEBUG-D] Data socket (port 7003): packet counts, queue/dequeue stats
- [DEBUG-FFMPEG] FFmpeg decoder: frame I/O, process health, ALL stderr
- [DEBUG-FFMPEG-STDERR] Raw FFmpeg stderr output
- [DEBUG-PCM] PCM data delivery with silence detection
- [DEBUG-AUDIO] AudioOutputService sample delivery and queue state
- [DEBUG-SETUP] AudioListener creation/stop lifecycle
- [DEBUG-TEARDOWN] Session teardown with stream type
- [DEBUG-UDP] Socket start/stop with port numbers

This will show exactly where audio data flow stops.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: thread-safe cipher/buffer, prevent silent handler crashes Fix screen mirroring audio: ADTS framing, cipher thread safety, debug logging Feb 10, 2026
Trace the full RTSP request/response flow to identify why iOS tears
down audio immediately during screen mirroring:

- [DEBUG-RTSP] logs every request type and response status
- [DEBUG-SETUP] logs all stream properties from SETUP requests
- [DEBUG-QUEUE] logs RaopBufferQueue decisions (no-data markers, old seqnums)
- [DEBUG-RTSP] logs unrecognized data that doesn't match any request pattern

Also add SETPEERS support:
- iOS sends SETPEERS requests for peer discovery
- Without a handler, these were silently dropped (no regex match, no response)
- This could cause iOS to think the connection is broken
- Added to RequestConst, RequestType enum, Request parser, and regex pattern

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: ADTS framing, cipher thread safety, debug logging Fix screen mirroring audio: ADTS framing, cipher thread safety, SETPEERS support, and debug logging Feb 10, 2026
…ecificConfig

ADTS headers can only encode AAC-Main/LC/SSR profiles (2-bit field) and
cannot represent AAC-ELD (AOT 39). FFmpeg was parsing AAC-ELD frames as
AAC-LC, causing "skip_data_stream_element: Input buffer exhausted" errors.

LOAS (Low Overhead Audio Stream, ISO 14496-3) carries a full
AudioSpecificConfig that properly signals AAC-ELD. The ASC (f8e85000)
matches UxPlay's reference implementation:
- AOT 39 (ER AAC-ELD)
- 44100 Hz
- 2 channels (stereo)
- frameLengthFlag = 1

FFmpeg demuxer changed from `-f aac` to `-f loas`.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: ADTS framing, cipher thread safety, SETPEERS support, and debug logging Fix screen mirroring audio: LOAS framing, thread safety, and SETUP response fixes Feb 10, 2026
… spf

Add [DEBUG-DECRYPT] logging to show first bytes of decrypted AAC-ELD
payload. Valid frames should start with 0x8c/0x8d/0x8e/0x80/0x81/0x82/0x20
(per UxPlay reference). This will help identify if AES-CBC decryption
is producing valid AAC-ELD data.

Also fix AudioSpecificConfig frameLengthFlag to match the actual frame
length from iOS SETUP (spf=480 → frameLengthFlag=0). Previously hardcoded
to 1 (512 samples) which mismatches the 480-sample frames iOS sends.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: LOAS framing, thread safety, and SETUP response fixes Fix screen mirroring audio: LOAS/LATM framing, AES cipher isolation, RTSP protocol gaps Feb 10, 2026
…bug logging

Replace FFmpeg subprocess + fragile ADTS/LOAS wrapping with direct native
FDK-AAC library calls via P/Invoke, matching the approach used by both
itskenny0/airplayreceiver and UxPlay reference implementations.

Key changes:
- NativeFdkAacEldDecoder: P/Invoke to libfdk-aac-2.dll with TT_MP4_RAW
  transport. Raw decrypted AAC-ELD frames fed directly, no container framing.
- AudioSpecificConfig: f8e85000 (matches UxPlay/itskenny0 exactly)
- Decoder fallback chain: native FDK-AAC → FFmpeg subprocess → SharpJaad
- Debug logging: [DEBUG-FDK] for decoder, [DEBUG-DECRYPT] for payload hex,
  [DEBUG-DECODE] for decode results with silence detection
- SETUP debug: shows unsigned port values for clarity

User needs libfdk-aac-2.dll in the application directory.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: LOAS/LATM framing, AES cipher isolation, RTSP protocol gaps Fix screen mirroring audio: native FDK-AAC decoder, AES-CBC cipher isolation, SETUP port handling Feb 10, 2026
…g + CI deps

Three fixes for screen mirroring audio stuttering:

1. RaopBufferDequeue: Change buflen from short to int (cast via ushort).
   When LastSeqNum - FirstSeqNum > 32767, short overflows negative,
   causing dequeue to think buffer is empty and stop processing.

2. Set no_resend=true during mirroring. Real-time audio should not wait
   for packet resends - this caused dequeue to get stuck on missing
   entries while the queue grew unboundedly, adding seconds of latency.

3. FlushAsync: Add _bufferLock synchronization for thread safety.

CI workflow: Add FFmpeg (ffmpeg.exe + ffplay.exe) and libfdk-aac-2.dll
to build artifacts so users don't need to download them separately.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Copilot AI changed the title Fix screen mirroring audio: native FDK-AAC decoder, AES-CBC cipher isolation, SETUP port handling Fix screen mirroring audio: native FDK-AAC decoder, buffer overflow fix, CI packaging Feb 10, 2026
Copilot AI and others added 2 commits February 10, 2026 21:34
1. CI workflow: Replace non-existent nicholastay/libfdk-aac-build with
   building FDK-AAC from source using MSYS2 (pre-installed on
   windows-latest runners). Uses mstorsjo/fdk-aac official source
   with CMake.

2. README.md: Complete rewrite reflecting current project state -
   FDK-AAC native decoder for mirroring audio, FFmpeg for video,
   simplified build instructions, architecture diagram.

3. README_zh.md: Chinese version of the README.

Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
Co-authored-by: YimingZhanshen <76594627+YimingZhanshen@users.noreply.github.com>
@YimingZhanshen
YimingZhanshen marked this pull request as ready for review February 10, 2026 21:55
Copilot AI review requested due to automatic review settings February 10, 2026 21:55
@YimingZhanshen
YimingZhanshen merged commit 517eec8 into main Feb 10, 2026
1 check passed
@YimingZhanshen
YimingZhanshen deleted the copilot/fix-screen-mirroring-sound branch February 10, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses missing audio during AirPlay screen mirroring by reworking the AAC-ELD decoding path, improving RAOP buffering behavior during mirroring, and updating CI packaging/docs to include required native dependencies.

Changes:

  • Added a native FDK-AAC AAC-ELD decoder (P/Invoke) with fallback to FFmpeg-based decoding.
  • Fixed RAOP buffer dequeue overflow behavior and adjusted mirroring mode to skip resend waits.
  • Updated CI to bundle FFmpeg + libfdk-aac, and refreshed English/Chinese READMEs.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 25 comments.

Show a summary per file
File Description
README.md Rewritten quick start, dependencies, and architecture notes (EN).
README_zh.md Added Chinese documentation with build/run instructions.
AirPlay/Services/AudioOutputService.cs Added more detailed audio output diagnostics.
AirPlay/Models/TcpListeners/Request.cs Added SETPEERS request type detection.
AirPlay/Models/Enums/RequestType.cs Added SETPEERS enum entry.
AirPlay/Models/Enums/RequestConst.cs Added SETPEERS magic-number constant.
AirPlay/Listeners/Bases/BaseUdpListener.cs Added UDP listener start/stop diagnostics and stored ports.
AirPlay/Listeners/Bases/BaseTcpListener.cs Added SETPEERS to regex splitter + extra RTSP diagnostics.
AirPlay/Listeners/AudioListener.cs Thread-safety improvements, mirroring resend behavior, buffer overflow fix, extensive debug logging.
AirPlay/Listeners/AirTunesListener.cs Pass mirroring flag into AudioListener; RTSP logging; OPTIONS advertises SETPEERS.
AirPlay/Decoders/Implementations/NativeFdkAacEldDecoder.cs New native FDK-AAC AAC-ELD decoder via P/Invoke.
AirPlay/Decoders/Implementations/FFmpegAacEldDecoder.cs Switched FFmpeg decoder to LOAS/LATM framing and improved diagnostics.
AirPlay/AirPlayService.cs Added PCM receive diagnostics before pushing to audio output.
.github/workflows/build.yml Downloads FFmpeg, builds libfdk-aac in CI, and bundles into artifacts.
Comments suppressed due to low confidence (30)

AirPlay/Listeners/AudioListener.cs:646

  • This comparison is always false.
                for (seqnum = raop_buffer.FirstSeqNum; Utilities.SeqNumCmp(seqnum, raop_buffer.LastSeqNum) < 0; seqnum++)

AirPlay/AirPlayService.cs:22

  • The contents of this container are never accessed.
        private List<byte> _audiobuf;

AirPlay/Listeners/Bases/BaseTcpListener.cs:126

  • Poor error handling: empty catch block.
                    catch (System.IO.IOException) { }

AirPlay/Services/AudioOutputService.cs:99

  • Poor error handling: empty catch block.
                        catch { }

AirPlay/Services/AudioOutputService.cs:242

  • Poor error handling: empty catch block.
                catch { }

AirPlay/Services/AudioOutputService.cs:276

  • Poor error handling: empty catch block.
                catch { }

AirPlay/Listeners/AudioListener.cs:642

  • Condition always evaluates to 'false'.
            if (Utilities.SeqNumCmp(raop_buffer.FirstSeqNum, raop_buffer.LastSeqNum) < 0)

AirPlay/Listeners/AirTunesListener.cs:344

  • Inefficient use of 'ContainsKey' and indexer.
                                if (stream.ContainsKey("audioFormat"))

AirPlay/Listeners/AirTunesListener.cs:352

  • Inefficient use of 'ContainsKey' and indexer.
                                if (stream.ContainsKey("ct"))

AirPlay/Listeners/AirTunesListener.cs:356

  • Inefficient use of 'ContainsKey' and indexer.
                                if (stream.ContainsKey("spf"))

AirPlay/Listeners/AirTunesListener.cs:360

  • Inefficient use of 'ContainsKey' and indexer.
                                if (stream.ContainsKey("controlPort"))

AirPlay/Listeners/AirTunesListener.cs:504

  • Inefficient use of 'ContainsKey' and indexer.
                if (request.Headers.ContainsKey("Content-Type"))

AirPlay/Listeners/AirTunesListener.cs:564

  • Inefficient use of 'ContainsKey' and indexer.
                if (request.Headers.ContainsKey("RTP-Info"))

AirPlay/Models/TcpListeners/Request.cs:210

  • Inefficient use of 'ContainsKey' and indexer.
            if(_headers.ContainsKey("CSeq"))

AirPlay/Listeners/Bases/BaseTcpListener.cs:121

  • String concatenation in loop: use 'StringBuilder'.
                        raw += string.Join(string.Empty, buffer.Take(readCount).Select(b => b.ToString("X2")));

AirPlay/Listeners/Bases/BaseTcpListener.cs:206

  • String concatenation in loop: use 'StringBuilder'.
                format += $"{header.Name}: {string.Join(",", header.Values)}\r\n";

AirPlay/Listeners/Bases/BaseTcpListener.cs:230

  • This assignment to e is useless, since its value is never read.
            catch (System.IO.IOException e)

AirPlay/Listeners/AirTunesListener.cs:201

  • This assignment to signatureBuffer is useless, since its value is never read.
                        signatureBuffer = aesCtr128Encrypt.ProcessBytes(signatureBuffer);

AirPlay/Listeners/AirTunesListener.cs:227

  • This assignment to mode is useless, since its value is never read.
                        var mode = body[14];

AirPlay/Listeners/AirTunesListener.cs:363

  • This assignment to controlPort is useless, since its value is never read.
                                    var controlPort = (ushort)((short)stream["controlPort"]);

AirPlay/Listeners/AirTunesListener.cs:423

  • This assignment to timingPort is useless, since its value is never read.
                                var timingPort = (ushort)((short)plist["timingPort"]);

AirPlay/Listeners/AirTunesListener.cs:526

  • This assignment to start is useless, since its value is never read.
                                var start = long.Parse(pVals[0]);

AirPlay/Listeners/AirTunesListener.cs:527

  • This assignment to current is useless, since its value is never read.
                                var current = long.Parse(pVals[1]);

AirPlay/Listeners/AirTunesListener.cs:528

  • This assignment to end is useless, since its value is never read.
                                var end = long.Parse(pVals[2]);

AirPlay/Listeners/AirTunesListener.cs:536

  • This assignment to image is useless, since its value is never read.
                        var image = request.Body;

AirPlay/Listeners/AirTunesListener.cs:543

  • This assignment to output is useless, since its value is never read.
                        var output = dmap.Decode(request.Body);

AirPlay/Decoders/Implementations/FFmpegAacEldDecoder.cs:381

  • These 'if' statements can be combined.
                    if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
                    {
                        if (!_ffmpegProcess.WaitForExit(PROCESS_EXIT_TIMEOUT_MS))
                        {
                            _ffmpegProcess.Kill();
                            _ffmpegProcess.WaitForExit(PROCESS_EXIT_TIMEOUT_MS);
                        }
                    }

AirPlay/Listeners/AudioListener.cs:34

  • Field '_raopBuffer' can be 'readonly'.
        private RaopBuffer _raopBuffer;

AirPlay/Models/TcpListeners/Request.cs:23

  • Field '_headers' can be 'readonly'.
        private HeadersCollection _headers;

AirPlay/Listeners/Bases/BaseTcpListener.cs:223

  • Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.
            if (bodyBuffer?.Any() == true)
            {
                payload = formatBuffer.Concat(bodyBuffer).ToArray();
            }
            else
            {
                payload = formatBuffer;
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +144 to +160
IntPtr ptr = Marshal.AllocHGlobal(pBuffer.Length);
try
{
Marshal.Copy(pBuffer, 0, ptr, pBuffer.Length);

IntPtr* pBufferPtr = stackalloc IntPtr[1];
pBufferPtr[0] = ptr;

int ret = (int)aacDecoder_Fill(_decoder, pBufferPtr, &bufferSize, &bytesValid);
if (ret == 0 && _decodeCallCount <= 5)
Console.WriteLine($"[DEBUG-FDK] Fill OK: bufferSize={pBuffer.Length}, bytesValid after={bytesValid}");
return ret;
}
finally
{
Marshal.FreeHGlobal(ptr);
}

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Fill allocates and frees unmanaged memory (AllocHGlobal/FreeHGlobal) on every frame. In real-time audio this can become a hotspot and cause jitter/glitches. Consider pinning the managed input buffer (fixed) or reusing a single unmanaged buffer across calls to avoid per-frame allocations and copies.

Suggested change
IntPtr ptr = Marshal.AllocHGlobal(pBuffer.Length);
try
{
Marshal.Copy(pBuffer, 0, ptr, pBuffer.Length);
IntPtr* pBufferPtr = stackalloc IntPtr[1];
pBufferPtr[0] = ptr;
int ret = (int)aacDecoder_Fill(_decoder, pBufferPtr, &bufferSize, &bytesValid);
if (ret == 0 && _decodeCallCount <= 5)
Console.WriteLine($"[DEBUG-FDK] Fill OK: bufferSize={pBuffer.Length}, bytesValid after={bytesValid}");
return ret;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
fixed (byte* bufferPtr = pBuffer)
{
IntPtr* pBufferPtr = stackalloc IntPtr[1];
pBufferPtr[0] = (IntPtr)bufferPtr;
int ret = (int)aacDecoder_Fill(_decoder, pBufferPtr, &bufferSize, &bytesValid);
if (ret == 0 && _decodeCallCount <= 5)
Console.WriteLine($"[DEBUG-FDK] Fill OK: bufferSize={pBuffer.Length}, bytesValid after={bytesValid}");
return ret;
}

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +267

private const string FDK_AAC_LIB = "libfdk-aac-2";

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The comments state Linux uses libfdk-aac.so.2, but FDK_AAC_LIB is hardcoded to libfdk-aac-2. On non-Windows platforms this name is unlikely to resolve without a matching symlink, so native decoding will fail even if the library is installed. Consider adding a platform-specific resolver (NativeLibrary.SetDllImportResolver) or updating the library name strategy/documentation to match actual SONAMEs.

Suggested change
private const string FDK_AAC_LIB = "libfdk-aac-2";
#if WINDOWS
private const string FDK_AAC_LIB = "libfdk-aac-2";
#elif LINUX
private const string FDK_AAC_LIB = "libfdk-aac.so.2";
#else
// Default to the common UNIX-like SONAME; adjust if targeting other platforms.
private const string FDK_AAC_LIB = "libfdk-aac.so.2";
#endif

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +43
- name: Download FFmpeg
shell: pwsh
run: |
$ffmpegUrl = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"
Invoke-WebRequest -Uri $ffmpegUrl -OutFile ffmpeg.zip
Expand-Archive -Path ffmpeg.zip -DestinationPath ffmpeg-tmp
# Copy ffmpeg.exe and ffplay.exe to publish directory
$binDir = Get-ChildItem -Path ffmpeg-tmp -Recurse -Directory -Filter "bin" | Select-Object -First 1
Copy-Item "$($binDir.FullName)\ffmpeg.exe" -Destination ./publish/win-x64/
Copy-Item "$($binDir.FullName)\ffplay.exe" -Destination ./publish/win-x64/

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

The workflow downloads a moving latest FFmpeg ZIP without any integrity verification (checksum/signature) and then executes the binaries. For supply-chain safety and reproducibility, consider pinning to a specific release/build (or at least validating a published SHA256) before copying into artifacts.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +53
- name: Build libfdk-aac from source
shell: bash
run: |
# Build FDK-AAC from official source using MSYS2 (pre-installed on windows-latest)
export MSYSTEM=MINGW64
export PATH="/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH"

# Install build dependencies
pacman -S --noconfirm --needed mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake make

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

This step assumes MSYS2 is already present at C:\msys64 and that pacman is available from the selected bash shell. To make CI more robust against runner image changes, consider installing MSYS2 explicitly via msys2/setup-msys2@v2 (and running the build in an MSYS2 shell), rather than relying on a preinstalled location.

Copilot uses AI. Check for mistakes.
Console.WriteLine($"[DEBUG-D] Packet #{dPacketCount}: type=0x{type_d:X2}, size={dret}");

buf_ret = RaopBufferQueue(_raopBuffer, packet, (ushort)dret, session);
if (packet.Length >= 12)

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

if (packet.Length >= 12) is always true because packet is a fixed 50k buffer. This should be checking the received byte count (dret >= 12) before reading header fields / decrypting / queueing, otherwise short/empty reads can still run through this path.

Suggested change
if (packet.Length >= 12)
if (dret >= 12)

Copilot uses AI. Check for mistakes.
{
var response = request.GetBaseResponse();

var cseq = request.Headers.ContainsKey("CSeq") ? request.Headers["CSeq"] : "?";

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Inefficient use of 'ContainsKey' and indexer.

Suggested change
var cseq = request.Headers.ContainsKey("CSeq") ? request.Headers["CSeq"] : "?";
request.Headers.TryGetValue("CSeq", out var cseq);
cseq ??= "?";

Copilot uses AI. Check for mistakes.
uint ntp_fraction = (uint)reader.ReadInt32();
ulong ntp_time = ((ulong)ntp_seconds * 1000000UL) + (((ulong)ntp_fraction * 1000000UL) >> 32);
uint rtp_timestamp = (uint)((packet[4] << 24) | (packet[5] << 16) | (packet[6] << 8) | packet[7]);
uint next_timestamp = (uint)((packet[16] << 24) | (packet[17] << 16) | (packet[18] << 8) | packet[19]);

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

This assignment to next_timestamp is useless, since its value is never read.

Suggested change
uint next_timestamp = (uint)((packet[16] << 24) | (packet[17] << 16) | (packet[18] << 8) | packet[19]);

Copilot uses AI. Check for mistakes.
Comment on lines 502 to 546
if (request.Type == RequestType.SET_PARAMETER)
{
if (request.Headers.ContainsKey("Content-Type"))
{
var contentType = request.Headers["Content-Type"];

if (contentType.Equals("text/parameters", StringComparison.OrdinalIgnoreCase))
{
var body = Encoding.ASCII.GetString(request.Body);
var keyPair = body.Split(":", StringSplitOptions.RemoveEmptyEntries).Select(b => b.Trim(' ', '\r', '\n')).ToArray();
if(keyPair.Length == 2)
{
var key = keyPair[0];
var val = keyPair[1];

if (key.Equals("volume", StringComparison.OrdinalIgnoreCase))
{
// request.Body contains 'volume: N.NNNNNN'
_receiver.OnSetVolume(decimal.Parse(val, CultureInfo.InvariantCulture));
}
else if (key.Equals("progress", StringComparison.OrdinalIgnoreCase))
{
var pVals = val.Split("/", StringSplitOptions.RemoveEmptyEntries);

var start = long.Parse(pVals[0]);
var current = long.Parse(pVals[1]);
var end = long.Parse(pVals[2]);

// DO SOMETHING W/ PROGRESS
}
}
}
else if (contentType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase))
{
var image = request.Body;

// DO SOMETHING W/ IMAGE
}
else if (contentType.Equals("application/x-dmap-tagged", StringComparison.OrdinalIgnoreCase))
{
var dmap = new DMapTagged();
var output = dmap.Decode(request.Body);
}
}
}

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

These 'if' statements can be combined.

Copilot uses AI. Check for mistakes.
private readonly object _bufferLock = new object();
private ulong _sync_time;
private ulong _sync_timestamp;
private ushort _controlSequenceNumber = 0;

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Field '_controlSequenceNumber' can be 'readonly'.

Suggested change
private ushort _controlSequenceNumber = 0;
private readonly ushort _controlSequenceNumber = 0;

Copilot uses AI. Check for mistakes.
private readonly DumpConfig _dConfig;

public AudioListener(IRtspReceiver receiver, string sessionId, ushort cport, ushort dport, DumpConfig dConfig) : base(cport, dport)
private bool _isMirroring = false;

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

Field '_isMirroring' can be 'readonly'.

Suggested change
private bool _isMirroring = false;
private readonly bool _isMirroring = false;

Copilot uses AI. Check for mistakes.
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.

3 participants