Add server-side waveform loudness profiling on track upload
ILoudnessAlgorithm strategy (RmsLoudnessAlgorithm first impl), WaveformProfileService stores quantized byte[] sidecar in new MediaFileVault (profiles vault), wired into UnifiedTrackService.UploadAsync; failure is logged and swallowed. WaveformProfileDto and WaveformProfileOptions in shared projects.
This commit is contained in:
@@ -45,6 +45,55 @@ public class AudioProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the raw PCM data region and format parameters from a WAV buffer, reusing the
|
||||
/// same chunk-walk and validation as metadata extraction. Returns null if the buffer is not
|
||||
/// a valid PCM WAV (callers treat a null as "no profile computable" and continue) — unlike
|
||||
/// <see cref="ExtractWavMetadata"/>, this does NOT fall back to synthetic defaults, because a
|
||||
/// loudness profile over fabricated silence would be misleading.
|
||||
/// </summary>
|
||||
public PcmData? TryExtractPcm(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
// Copy the span to an array so the existing array-based parsers can be reused. The PCM
|
||||
// slice returned is a view over this array (no second copy of the data region).
|
||||
var bytes = buffer.ToArray();
|
||||
|
||||
var validation = ValidateWavStructure(bytes);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
WavMetadata metadata;
|
||||
try
|
||||
{
|
||||
metadata = ParseWavMetadata(bytes, validation);
|
||||
ValidateAudioParameters(metadata);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Data bytes begin 8 past the "data" chunk id (4 id + 4 size). Clamp the declared size to
|
||||
// what is actually present — some encoders write a size that overshoots the file.
|
||||
var dataStart = validation.DataChunkPos + 8;
|
||||
if (dataStart > bytes.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var available = bytes.Length - dataStart;
|
||||
var dataLength = Math.Min(metadata.DataSize, available);
|
||||
if (dataLength <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pcm = new ReadOnlyMemory<byte>(bytes, dataStart, dataLength);
|
||||
return new PcmData(pcm, metadata.Channels, metadata.SampleRate, metadata.BitsPerSample);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts metadata from WAV file buffer with comprehensive validation
|
||||
/// </summary>
|
||||
@@ -268,4 +317,18 @@ public class AudioProcessor
|
||||
public int FmtChunkPos { get; set; }
|
||||
public int DataChunkPos { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The raw PCM sample region of a WAV plus the format parameters needed to interpret it.
|
||||
/// <see cref="Pcm"/> is a view over the decoded buffer — the data chunk only, header excluded.
|
||||
/// </summary>
|
||||
/// <param name="Pcm">The PCM sample bytes (interleaved by channel, little-endian).</param>
|
||||
/// <param name="Channels">Number of interleaved channels.</param>
|
||||
/// <param name="SampleRate">Samples per second.</param>
|
||||
/// <param name="BitsPerSample">Bit depth per sample (8, 16, 24, or 32).</param>
|
||||
public readonly record struct PcmData(
|
||||
ReadOnlyMemory<byte> Pcm,
|
||||
int Channels,
|
||||
int SampleRate,
|
||||
int BitsPerSample);
|
||||
Reference in New Issue
Block a user