# Distinguish absent audio from silence and verify an AAC export

Verify that a required audio stream exists and survives AAC conversion. Numerical decoding and RMS checks are not listening, intelligibility or sound-quality reviews.

This complete kit uses Node standard-library modules only. Install/use your own FFmpeg; the binary is not redistributed. Set FFMPEG_BIN to its absolute path if it is not on PATH.

Run from this directory and select a NEW output directory. Existing output directories are refused. On a memory-managed workstation, put its local serialization wrapper before the Node command. No provider credentials or off-device fixture requests are used.

```sh
node run-recipe.mjs --out ./my-run
```

Recorded execution: 7 checks passed, including 1 expected rejections. Node v24.18.0. ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers

Preparation

- Use Node.js and FFmpeg with AAC support, setting FFMPEG_BIN as needed. No external music or speech is downloaded.
- Inspect the three distinct inputs: an original 440-Hz sine WAV, digital-silence WAV and one-second video with no audio. The silent WAV contains 16,000 zero samples; absent audio has no stream to map.
- Choose a fresh output directory and keep initial-failed-run.json for the documented first attempt. The corrected recorded run avoids -shortest during stream copy and preserves all ten video frames.

## 01. Create three different audio conditions

The script writes the tone and silence WAV headers and PCM samples directly with Node buffers. It muxes each with the original video into separate MKV fixtures. A present stream containing zeros is not the same as a file that lacks an audio stream.

## 02. Require an explicit audio mapping

Map 0:v:0 and 0:a:0 when creating aac-export.mp4, copying video and encoding audio with AAC at 64 kbit/s. The absent-audio fixture deliberately fails the required map instead of accepting an optional missing stream.

## 03. Decode samples and check measured energy

Decode AAC to mono 16-kHz signed 16-bit PCM. The actual run returned 16,384 samples and RMS 0.1391365 for the synthetic tone. The count accepts bounded AAC padding; it does not claim an exact presentation duration from raw padded samples.

## 04. Separate codec, silence and video checks

The actual inspection log reports Audio: aac; all ten video frames decode, and the silent-track fixture decodes to PCM samples all valued zero. The initial run’s -shortest video truncation is retained as a failed receipt; the corrected script’s seven checks all pass.

Acceptance

- The exported audio maps successfully and decodes to nonempty, even-length 16-bit PCM.
- Decoded AAC count is between 16,000 and 17,024 samples; the recorded count is 16,384.
- The original sine’s decoded RMS lies between 0.1 and 0.2; the recorded value is 0.1391365.
- The actual FFmpeg inspection names AAC as the audio codec.
- The exported video still contains ten complete 320×180 frames.
- A present silent track decodes to zeros, while absent audio fails the explicit stream map; seven checks pass.

Troubleshooting

A supposedly silent file fails because no audio stream is present. Inspect only the required stream mapping. A silence waveform needs an actual track; choose the provided silent-track fixture to test that separate condition.

The sample count slightly exceeds one second. Keep the 16-kHz output rate fixed and compare the bounded AAC padding allowance. Do not label all decoded padded samples as exact display duration.

The copied video ends early after muxing. Remove only -shortest from the equal-duration fixture stream copy and verify all ten video frames again. The initial failed receipt records this observed issue.

Public receipt copies normalize only local checkout paths and include publicPathNormalization with the original receipt SHA-256; raw originals are retained outside public assets. The actual receipts are under executed/. failure-receipt.json lists expected negative cases, not failed-suite assertions. Original tiny fixtures are generated again into your new output directory; shipped executed inputs and outputs let you inspect the recorded run. Subprocesses have a 15-second timeout, an 8-MB output cap, argument arrays and no shell. Temporary raw buffers and local HTTP servers are cleaned in finally.

checksums.json records every distributed file except itself and the archive, avoiding self-reference. The ZIP includes that same checksum list. Recompute hashes after download; comparison requires trusting the expected list. execution-receipt.json records executed script hashes before and after the run. The complete kit archive is also bound by the publishing catalog asset inventory.

Retain stream logs and measured samples as machine checks. Listen and review synchronization separately before making an editorial judgement about audio; this recipe did not perform either review.

Sources read on 2026-10-14

- FFmpeg command-line documentation: maps, codecs and error exit: https://ffmpeg.org/ffmpeg.html
- Node.js child process API: bounded argument-array execution: https://nodejs.org/api/child_process.html

The initial failed run is preserved in initial-failed-run.json; the corrected final run is in executed/.
