Decoding GSM WAV Files with Java
Explains a Java codec chain that converts WAV streams containing A-law, μ-law, or PCM into a 16 kHz PCM contract. Channel preservation, resampling, VAD/ASR ordering, and forensic-use boundaries are examined.
Processing a WAV file originating from telecommunications is not a matter of reading the file extension and treating its samples directly as audio amplitudes. WAV is a container. It may contain linear PCM, A-law, μ-law, or audio encoded with another codec. In call recordings, PBX outputs, and files obtained from legacy telecommunications systems in particular, the container and sample encoding must be distinguished.
The primary objective of the Java library I developed was to remove this distinction from the application layer. By examining different codec implementations and commonly used conversion code, I combined A-law, μ-law, and PCM paths within one streaming model. I used the library not only to play files, but to provide stable audio streams to VAD and automatic speech-recognition systems. Regardless of the input format, the target is 16 kHz, 16-bit signed PCM in mono or stereo as required.
This structure allowed me to move audio from different recording systems encountered in digital-forensics work into a common analysis plane. A transformed stream must nevertheless always be treated separately as derivative data produced for analysis rather than as a replacement for the original evidence.
Limits of the term GSM WAV
In institutional applications, 8 kHz telephone audio is often referred to generally as "GSM WAV." This usage is operationally understandable but technically imprecise. GSM Full Rate, G.711 A-law, and G.711 μ-law are different coding systems.
The RTP profile defines GSM, PCMA, and PCMU as separate codecs. Static payload type 0 is used for PCMU, 3 for GSM, and 8 for PCMA. PCMA and PCMU are logarithmically scaled eight-bit-per-sample audio encodings under ITU-T G.711. GSM Full Rate is a separate frame-based speech codec defined in ETSI GSM 06.10, now within the 3GPP TS 46.010 family.
The WAV container also carries more than PCM. Microsoft's WAVEFORMATEX definition contains separate format tags for A-law and μ-law in addition to PCM. The .wav extension therefore does not by itself show that the content is linear PCM. The encoding type in the fmt section, sampling rate, channel count, frame size, and bit depth must be examined together.
The low-level codec core in the shared source code does not decode a GSM 06.10 bitstream. If the stream is A-law or μ-law, it uses its own G.711 converter. Other recognized audio formats are delegated to the Java Sound conversion layer. Although the class name reflects the telecommunications domain in which the application operates, the specific codec implementation shown is G.711 A-law and μ-law decoding.
This distinction is particularly important in forensic reports. Obtaining a file from a GSM system does not show that its audio is encoded with GSM Full Rate. Source system, container, and codec must be identified separately.
PCM and logarithmic companding
In linear PCM, every sample represents signal amplitude at a particular instant on a linear numeric scale. For 16-bit signed PCM, samples are approximately in the range -32768 to 32767. Values near zero represent silence, while large absolute values indicate greater amplitude.
Encoding the entire range with equal resolution is inefficient for telephone audio. Small differences in low-amplitude parts of speech are perceptually important, while the same absolute quantization step is less noticeable at high amplitudes. Companding therefore compresses dynamic range logarithmically before encoding and expands it again during decoding.
G.711 is a telecommunications standard with a long history for PCM coding of voice frequencies. Versions date to 1972, while the principal current recommendation is maintained through the 1988 version and subsequent appendices. ITU-T also states that reference C code for G.711 is available in the G.191 software tools.
A-law and μ-law use different segment and quantization rules for the same purpose. In both formats, one sample becomes an eight-bit codeword. In a traditional 8 kHz telephone stream, this produces 64,000 bits per second per channel. The RTP definitions for PCMA and PCMU likewise specify eight-bit samples after logarithmic scaling.
These encodings are not codecs such as MP3 or GSM Full Rate that build predictions across temporal frames. Every sample is an independent companding code. This property makes decoding extremely inexpensive. One byte can be converted into a linear PCM sample by reading the corresponding value from a 256-element table.
My library uses lookup tables mapping eight-bit A-law and μ-law codes to 16-bit linear values. Each input sample is decoded with one table access and written to the output buffer in the requested byte order. The same core also supports signed or unsigned eight-bit PCM output and direct conversion between A-law and μ-law.
For a total sample count S, fundamental decoding cost is:
\[ T(S)=\Theta(S) \]
Every sample is read once and written once. Because tables have fixed size, additional algorithmic space cost is:
\[ M(S)=\Theta(1) \]
The output buffer is excluded from this calculation.
Streaming codec architecture
The main design decision was to build conversion as a codec chain operating through AudioInputStream rather than as a function loading the complete file into memory. FormatConversionProvider is the fundamental Java Sound interface for codec and transcoder layers that convert between input and output formats. It reports which target formats can be produced from a source format and creates a new audio stream from which converted data can be read.
The provider I developed accepts eight-bit A-law and μ-law sources. It can produce signed or unsigned eight-bit PCM, and big-endian or little-endian signed 16-bit PCM. Sampling rate, frame rate, and channel count are inherited from the source. The companding-decoding stage therefore does not change the time axis or channel structure.
The stream's read path calculates frame count according to the target frame size. The number of source bytes required is determined from that value. If an output sample is not larger than the input sample, conversion can occur in the same buffer. Otherwise, a reusable internal buffer is used. A new large array is not allocated for every read.
This structure is important for long recordings. A call recording lasting several hours can be processed without loading it entirely into memory through the following chain:
\[ \text{file stream} \rightarrow \text{WAV parsing} \rightarrow \text{G.711 decoding} \rightarrow \text{resampling} \rightarrow \text{VAD or ASR} \]
A one-mebibyte buffer is placed in front of the source file. This reduces the number of file-system calls during long sequential reads. The conversion layer also expands its working buffer only when necessary and reuses it for subsequent reads.
For high-volume use, the block-reading path should be preferred over the single-byte read(). The single-byte path creates a temporary byte array, while read(byte[], offset, length) operates on a reusable caller buffer. The difference is negligible for one short file but affects allocation behavior in VAD and ASR pipelines processing thousands of recordings.
Multichannel sample processing
In audio recording, "multichannel" does not only mean stereo music. In call systems, parties can be on separate channels. In-vehicle recordings can place cabin and radio on different channels. Courtroom or meeting systems can use multiple microphones as independent channels. SWGDE specifically notes that mono, stereo, dual-mono, and independent multichannel recordings can affect forensic examination and enhancement results.
In the codec core, frame count is multiplied by channel count to obtain the actual sample count. A-law and μ-law decoding therefore applies to all channels in the interleaved frame rather than only the first channel.
The FloatSampleBuffer layer also stores channels in separate floating-point arrays. Interleaved byte data is advanced by frame size and converted into independent normalized samples for each channel. Eight-, 16-, 24-, and 32-bit signed PCM, eight-bit unsigned PCM, and big-endian and little-endian representations are treated as separate format types. Layouts storing 24-bit samples in three or four bytes are also parsed.
The high-level GsmWavDecoderStream deliberately reduces output to one of two contracts:
16 kHz, 16-bit, mono, little-endian PCM
16 kHz, 16-bit, stereo, little-endian PCM
If the source is A-law or μ-law, it is first decoded into 16-bit PCM while preserving channel count. A target mono or stereo stream is then created according to the mono option and source channel count. The core therefore processes multichannel input but exposes one- or two-channel standardized output for VAD and ASR rather than an arbitrary channel count.
This separation was useful in my work. The source structure could be preserved while channel information remained relevant. The subsequent analysis pipeline could use a fixed PCM contract known in advance to be mono or stereo.
Meaning of the 16 kHz target
Converting an 8 kHz G.711 recording to 16 kHz does not recover high-frequency information absent from the recording. By the Nyquist limit, an 8 kHz sampled signal can carry information only up to 4 kHz. The traditional telephone band used with G.711 is also approximately 300 to 3400 Hz.
Resampling to 16 kHz should therefore not be considered an audio-enhancement operation. Its primary purpose is to place different sources into the common timing and sample layout expected by VAD and ASR systems.
In a 16 kHz stream:
\[ 10\ \text{ms}=160\ \text{samples} \]
\[ 20\ \text{ms}=320\ \text{samples} \]
\[ 30\ \text{ms}=480\ \text{samples} \]
This fixed relation simplifies frame-based voice-activity detection, feature extraction, and speech recognition. If inputs arrive at 8, 11.025, 22.05, or 44.1 kHz, downstream components do not each need separate sampling-rate calculations.
The code uses Java Sound's AudioSystem.getAudioInputStream(targetFormat, sourceStream) mechanism for the final conversion. Java documentation states that the call returns a new stream if a format converter capable of producing the requested target format exists and throws IllegalArgumentException otherwise. The G.711 decoder is therefore under the library's own control, while availability of sampling-rate and channel conversion depends on Java Sound providers in the runtime environment.
For institutional deployment, I did not rely only on a file opening successfully on the development computer. The format-conversion matrix must be verified on the target operating system and JDK distribution. Automated tests with known input and output samples are safer in critical workflows.
Correct ordering for VAD and ASR
Interpreting A-law or μ-law bytes directly as signed PCM samples is incorrect. Eight-bit codewords do not represent linear amplitude. Their bit fields carry sign, segment, and quantization information. Companding must therefore be decoded before calculating energy, RMS, peak value, zero crossings, or spectral features.
The correct processing order is:
\[ \text{A-law or μ-law} \rightarrow \text{linear PCM} \rightarrow \text{channel policy} \rightarrow \text{resampling} \rightarrow \text{VAD} \rightarrow \text{ASR} \]
The structure I developed places this order within the stream chain. Bytes delivered to the user layer are no longer codec-specific telephone codes but standard 16-bit PCM samples.
Channel policy must be selected deliberately before VAD. If two channels contain different speakers, direct averaging can produce amplitude cancellation or mixing during overlapping speech. Channel-level VAD, selection of the more active channel, or separate ASR streams for the two channels can be more appropriate. Mono conversion should not be enabled automatically before this decision.
Defining mono and stereo targets separately provides a useful basis. The same decoding core can support a single-channel ASR input or a stereo analysis path preserving channel separation.
Boundary of digital-forensics use
In forensic audio examination, the converted PCM file or stream is not the original recording. The original file must be preserved with its container, codec, metadata, time information, and hash. SWGDE recommends working from a bitstream copy of the digital original, preserving the original format and metadata, and verifying copies through hashing.
The 16 kHz PCM stream produced by this library is an analysis copy. It can be used for VAD, speech recognition, speaker segmentation, listening, or spectral analysis. The following information should nevertheless be recorded separately:
Hash of the original file
Original codec and container
Original sampling rate
Bit depth
Channel count and layout
Applied A-law or μ-law decoding
Resampling method
Mono or stereo conversion decision
Software and version used
SWGDE likewise requires documentation of sampling rate, bit depth, channel count, file format, codec, and format conversions. It notes that unnecessary transcoding and resampling can create audio artifacts and recommends uncompressed PCM where conversion is required, with anti-aliasing settings recorded.
Table-based A-law and μ-law decoding into PCM is deterministic and reproducible. The same eight-bit code maps to the same PCM value. Resampling depends on filter design, boundary behavior, and provider implementation. The original recording must therefore be preserved for forensic comparison, and the resampled version used only for the stated analysis purpose.
Engineering limitations of the source code
The primary A-law and μ-law decoding path is simple and inexpensive. Several boundaries should be managed explicitly in a general-purpose version.
First, conversion to 16 kHz and mono or stereo depends on the Java Sound provider. If the deployment environment does not offer the target conversion, processing fails. In systems requiring independent, bit-identical results, the resampler should also be included within the application.
Second, one of the shown buffer-to-buffer conversion paths between A-law and μ-law requires additional testing of input and output offset roles when nonzero offsets are used. This path is separate from the primary A-law or μ-law to PCM chain used by GsmWavDecoderStream. Different offsets and same-buffer scenarios should nevertheless be covered by codec-core tests.
Third, an IOException occurring in the floating-point buffer read path is treated like an empty sample array. In a continuous ASR service, this can simplify stream termination. In a forensic application, actual EOF should be distinguished from a read error and the error recorded.
These points do not alter the fundamental algorithm. The strength of the library is that it combines codec decoding, channel awareness, frame alignment, buffer reuse, and target PCM normalization within one readable streaming contract.
The primary expertise in this work does not come from inventing a new companding standard. It comes from placing G.711, PCM, the WAV container, the Java Sound conversion model, and speech-processing requirements in the correct order within the same data pipeline.
The benefit I obtained in institutional and digital-forensics work arose from this integration. I first decoded telecommunications recordings with the correct codec, evaluated them without losing channel structure, and provided VAD and ASR components with a fixed 16 kHz PCM contract. File-format differences therefore did not propagate into upper-layer algorithms, and the audio-processing pipeline became more predictable.