mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-12 09:21:35 -10:00
formats/wma: WMA1 and WMA2 played gapless
WMA1 and WMA2 carry no gapless signalling, and rwma played them as the frames came: the encoder's priming frame first (FFmpeg's encoder sets initial_padding to one frame, as WMA encoders put one in), and without the last frame's second half, which only the next frame's overlap would have completed. A track started one frame late - 46 ms at 44.1 kHz - and lost its last frame. FFmpeg's decoder is no reference here: it drops two frames at the start, its own and one of the audio. - rwma_decode_packet()'s drain (no bytes) gives the standard decoder's overlap tail once, as FFmpeg's flush does, where it gave nothing; - rwma_stream drops the first frame of a WMA1 or WMA2 stream, drains the tail at the end, and stops at the length the ASF header states (rwma_stream_frames), so the stream plays exactly the audio the encoder was given. WMA Pro (which signals its own trims) and XMA are as they were; a rewind starts the trimming again. parallel-ps3 needs this for cellMusicDecode to play WMA tracks from their first sample to their last. audio_transfer_sniff_test --file gains --source, the decode against the signal the encoder was given: from its first sample, at its length within 2 ms (ASF's durations are in milliseconds), 20 dB above the coding error. CI checks WMA1 and WMA2 from FFmpeg at 44.1, 22.05, 16 and 8 kHz (frames of 2048, 1024 and 512 samples); the old code was a frame late and 575 samples long at 44.1 kHz. rwma_test checks the drain gives the tail once, and audio_transfer_wma_test, whose fixture is a single coded frame, now expects that frame's second half.
This commit is contained in:
1 parent
100a62c152
commit
a2cd6efcb4
6 files changed
+167
-11
No files matched your search
@@ -1093,6 +1093,19 @@ jobs:
|
||||
-of csv=p=0 "$work/$f" | tr '\n' ' ' | awk '{ printf "%d", $1 * $2 + 0.5 }')
|
||||
./audio_transfer_sniff_test --file "$work/$f" wma --frames "$frames"
|
||||
done
|
||||
# WMA1 and WMA2, gapless at each frame size (2048, 1024 and 512
|
||||
# samples): the signal FFmpeg's encoder was given, from its
|
||||
# first sample, at the length the header states
|
||||
for codec in wmav1 wmav2; do
|
||||
for rate in 44100 22050 16000 8000; do
|
||||
src="sine=f=440:d=0.73:sample_rate=$rate"
|
||||
ffmpeg -nostdin -hide_banner -loglevel error -f lavfi -i "$src" -ac 2 \
|
||||
-c:a "$codec" -y "$work/g.wma"
|
||||
ffmpeg -nostdin -hide_banner -loglevel error -f lavfi -i "$src" -ac 2 \
|
||||
-f f32le -y "$work/g.f32"
|
||||
./audio_transfer_sniff_test --file "$work/g.wma" wma --source "$work/g.f32"
|
||||
done
|
||||
done
|
||||
six=""
|
||||
n=0
|
||||
for hz in 300 500 700 80 1100 1300; do
|
||||
|
||||
@@ -628,14 +628,27 @@ int rwma_std_decode(void *opaque, const uint8_t *data, size_t bytes, size_t *use
|
||||
unsigned step;
|
||||
if (s->failed)
|
||||
return RWMA_BAD_DATA;
|
||||
std_roll(s);
|
||||
if (!bytes)
|
||||
{
|
||||
/* The end of the stream: the last frame's second half, which the
|
||||
* next frame's overlap would have completed, comes out once */
|
||||
unsigned c, had = s->pending;
|
||||
if (s->in_packet)
|
||||
return RWMA_BAD_DATA;
|
||||
std_roll(s);
|
||||
if (had && !s->drained)
|
||||
{
|
||||
out->samples = s->length;
|
||||
out->channels = s->config.channels;
|
||||
out->sample_rate = s->config.sample_rate;
|
||||
for (c = 0; c < s->config.channels; c++)
|
||||
out->planes[c] = s->acc[c];
|
||||
s->pending = 1;
|
||||
}
|
||||
s->drained = 1;
|
||||
return RWMA_OK;
|
||||
}
|
||||
std_roll(s);
|
||||
if (s->drained || !data || bytes < s->config.block_align ||
|
||||
(s->in_packet && data != s->packet))
|
||||
return RWMA_BAD_DATA;
|
||||
|
||||
@@ -44,6 +44,10 @@ struct rwma_stream
|
||||
/* the length ASF states: its play duration less its preroll, in
|
||||
* 100 ns units */
|
||||
uint64_t play_100ns;
|
||||
/* WMA1 and WMA2 played gapless: the encoder's priming frame dropped,
|
||||
* the last frame's tail drained, the end at the stated length */
|
||||
unsigned gapless, primed, tail_done;
|
||||
uint64_t skip_left, emitted;
|
||||
};
|
||||
|
||||
static uint32_t stream_u32(const uint8_t *p)
|
||||
@@ -284,6 +288,7 @@ int rwma_stream_open(rwma_stream **out, const uint8_t *data, size_t size, size_t
|
||||
rwma_stream_close(s);
|
||||
return status;
|
||||
}
|
||||
s->gapless = !s->xma_streams && (cfg.codec == RWMA_V1 || cfg.codec == RWMA_V2);
|
||||
s->channels = cfg.channels;
|
||||
s->rate = cfg.sample_rate;
|
||||
s->align = cfg.block_align;
|
||||
@@ -463,7 +468,7 @@ static int stream_object(rwma_stream *s)
|
||||
return RWMA_STREAM_WAIT;
|
||||
}
|
||||
|
||||
int rwma_stream_next(rwma_stream *s, rwma_frame *frame)
|
||||
static int stream_next_raw(rwma_stream *s, rwma_frame *frame)
|
||||
{
|
||||
unsigned steps;
|
||||
if (!frame)
|
||||
@@ -531,6 +536,61 @@ invalid:
|
||||
return RWMA_BAD_DATA;
|
||||
}
|
||||
|
||||
/* WMA1 and WMA2 carry no gapless signalling, but their encoders put one
|
||||
* frame of priming before the audio (FFmpeg's sets initial_padding to a
|
||||
* frame), and the decoder's output lags by the overlap, the last frame's
|
||||
* second half coming out only when the stream is drained. So the first
|
||||
* frame is dropped, the tail drained, and the end cut at the length the
|
||||
* ASF header states, which is the audio the encoder was given. */
|
||||
int rwma_stream_next(rwma_stream *s, rwma_frame *frame)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
int status = stream_next_raw(s, frame);
|
||||
uint64_t total;
|
||||
unsigned c, n;
|
||||
if (!s || !s->gapless)
|
||||
return status;
|
||||
if (status == RWMA_STREAM_END && !s->tail_done)
|
||||
{
|
||||
size_t used = 0;
|
||||
s->tail_done = 1;
|
||||
if (rwma_decode_packet(s->decoder, NULL, 0, &used, frame) || !frame->samples)
|
||||
return RWMA_STREAM_END;
|
||||
status = RWMA_OK;
|
||||
}
|
||||
if (status)
|
||||
return status;
|
||||
if (!s->primed)
|
||||
{
|
||||
s->primed = 1;
|
||||
s->skip_left = frame->samples;
|
||||
}
|
||||
if (s->skip_left)
|
||||
{
|
||||
n = s->skip_left < frame->samples ? (unsigned)s->skip_left : frame->samples;
|
||||
for (c = 0; c < frame->channels; c++)
|
||||
frame->planes[c] += n;
|
||||
frame->samples -= n;
|
||||
s->skip_left -= n;
|
||||
if (!frame->samples)
|
||||
continue;
|
||||
}
|
||||
if ((total = rwma_stream_frames(s)) != 0)
|
||||
{
|
||||
if (s->emitted >= total)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
return RWMA_STREAM_END;
|
||||
}
|
||||
if (frame->samples > total - s->emitted)
|
||||
frame->samples = (unsigned)(total - s->emitted);
|
||||
}
|
||||
s->emitted += frame->samples;
|
||||
return RWMA_OK;
|
||||
}
|
||||
}
|
||||
|
||||
void rwma_stream_rewind(rwma_stream *s)
|
||||
{
|
||||
unsigned i;
|
||||
@@ -552,6 +612,8 @@ void rwma_stream_rewind(rwma_stream *s)
|
||||
s->assembled = s->object_pos = s->object_size = 0;
|
||||
s->compressed_at = s->compressed_end = s->demand = 0;
|
||||
s->object = NULL;
|
||||
s->primed = s->tail_done = 0;
|
||||
s->skip_left = s->emitted = 0;
|
||||
}
|
||||
void rwma_stream_set_avail(rwma_stream *s, size_t avail)
|
||||
{
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
* and look-alikes it must turn down; every prefix of each, and each
|
||||
* byte corrupted, read inside the buffer; the fold tables.
|
||||
* audio_transfer_sniff_test --file FILE TYPE [--tones] [--pcm REF]
|
||||
* [--frames N]
|
||||
* [--frames N] [--source REF]
|
||||
* FILE sniffed as TYPE (wav flac vorbis opus mp3 aac ac3 wma mod),
|
||||
* started where the sniffer says and decoded to the end. With
|
||||
* --pcm the decode is FFmpeg's (REF, f32le at the file's rate):
|
||||
* the same length, sample for sample within 1e-4 - where the audio
|
||||
* starts and ends, gapless trims and all. With --frames the length
|
||||
* audio_transfer_info states is N. With
|
||||
* audio_transfer_info states is N. With --source the decode is
|
||||
* the signal the encoder was given (REF, f32le): where it starts,
|
||||
* to the sample, its length within 2 ms (what ASF's millisecond
|
||||
* durations lose), and at least 20 dB above the coding error -
|
||||
* for codecs FFmpeg's decoder is not gapless for. With
|
||||
* --tones FILE is 5.1 with a tone in each channel - FL 300 Hz,
|
||||
* FR 500, FC 700, LFE 80, BL 1100, BR 1300 - and the decode folded
|
||||
* with audio_transfer_stereo_matrix has the left tones on the left
|
||||
@@ -250,7 +254,7 @@ static double tone(const float *x, size_t n, double hz, double rate)
|
||||
}
|
||||
|
||||
static int run_file(const char *path, const char *want, int tones,
|
||||
const char *pcm_path, long want_frames)
|
||||
const char *pcm_path, long want_frames, const char *src_path)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
uint8_t *buf;
|
||||
@@ -341,6 +345,60 @@ static int run_file(const char *path, const char *want, int tones,
|
||||
free(ref);
|
||||
}
|
||||
|
||||
if (src_path)
|
||||
{
|
||||
FILE *rf = fopen(src_path, "rb");
|
||||
long rlen, lag, best_lag = 0;
|
||||
float *ref;
|
||||
size_t i, rn, n;
|
||||
double best = -1e300, sig = 0, err = 0;
|
||||
unsigned c;
|
||||
if (!rf)
|
||||
return 1;
|
||||
fseek(rf, 0, SEEK_END);
|
||||
rlen = ftell(rf);
|
||||
fseek(rf, 0, SEEK_SET);
|
||||
ref = (float*)malloc((size_t)rlen + 1);
|
||||
if (fread(ref, 1, (size_t)rlen, rf) != (size_t)rlen)
|
||||
return 1;
|
||||
fclose(rf);
|
||||
rn = (size_t)rlen / sizeof(float) / ch;
|
||||
n = rn < frames ? rn : frames;
|
||||
/* where the decode lines up with the source */
|
||||
for (lag = -4096; lag <= 4096; lag++)
|
||||
{
|
||||
double dot = 0;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
long j = (long)i + lag;
|
||||
if (j < 0 || (size_t)j >= frames)
|
||||
continue;
|
||||
for (c = 0; c < ch; c++)
|
||||
dot += (double)pcm[(size_t)j * ch + c] * ref[i * ch + c];
|
||||
}
|
||||
if (dot > best)
|
||||
{
|
||||
best = dot;
|
||||
best_lag = lag;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < n * ch; i++)
|
||||
{
|
||||
sig += (double)ref[i] * ref[i];
|
||||
err += ((double)pcm[i] - ref[i]) * ((double)pcm[i] - ref[i]);
|
||||
}
|
||||
if ( best_lag
|
||||
|| (frames > rn ? frames - rn : rn - frames) > rate / 500
|
||||
|| !(sig > 0 && 10 * log10(sig / (err + 1e-30)) >= 20))
|
||||
{
|
||||
fprintf(stderr, "%s: %ld frames from the source, %u frames to its %u, %.1f dB above the error\n",
|
||||
path, best_lag, (unsigned)frames, (unsigned)rn,
|
||||
sig > 0 ? 10 * log10(sig / (err + 1e-30)) : 0.0);
|
||||
bad = 1;
|
||||
}
|
||||
free(ref);
|
||||
}
|
||||
|
||||
if (tones)
|
||||
{
|
||||
static const double hz[6] = { 300, 500, 700, 80, 1100, 1300 };
|
||||
@@ -390,7 +448,7 @@ static int run_file(const char *path, const char *want, int tones,
|
||||
if (!bad)
|
||||
printf("audio_transfer_sniff_test: %s: %s, %u channels at %u Hz, %u frames%s%s\n",
|
||||
path, want, ch, rate, (unsigned)frames, tones ? ", each tone where it belongs" : "",
|
||||
pcm_path ? ", FFmpeg's samples" : "");
|
||||
pcm_path ? ", FFmpeg's samples" : src_path ? ", the source's timing" : "");
|
||||
audio_transfer_free(ctx, t);
|
||||
free(pcm);
|
||||
free(buf);
|
||||
@@ -402,7 +460,7 @@ int main(int argc, char **argv)
|
||||
if (argc >= 4 && !strcmp(argv[1], "--file"))
|
||||
{
|
||||
int i, tones = 0;
|
||||
const char *pcm = NULL;
|
||||
const char *pcm = NULL, *src = NULL;
|
||||
long frames = -1;
|
||||
for (i = 4; i < argc; i++)
|
||||
{
|
||||
@@ -412,8 +470,10 @@ int main(int argc, char **argv)
|
||||
pcm = argv[++i];
|
||||
else if (!strcmp(argv[i], "--frames") && i + 1 < argc)
|
||||
frames = atol(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--source") && i + 1 < argc)
|
||||
src = argv[++i];
|
||||
}
|
||||
return run_file(argv[2], argv[3], tones, pcm, frames);
|
||||
return run_file(argv[2], argv[3], tones, pcm, frames, src);
|
||||
}
|
||||
return run_checks();
|
||||
}
|
||||
@@ -19,13 +19,17 @@ static void check(int valid, const char *what)
|
||||
}
|
||||
}
|
||||
#include "wma_fixture.h"
|
||||
/* The fixture is one coded frame. WMA1 and WMA2 play gapless: that
|
||||
* frame's first half is the encoder's priming, dropped, and its second
|
||||
* half - the overlap tail - comes out when the stream drains: what
|
||||
* plays is the second half of the windowed transform. */
|
||||
static void expected(void)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < 2048; i++)
|
||||
{
|
||||
double transform = -cos(PI / 2048 * (i + 0.5 + 1024) * 0.5);
|
||||
double window = sin((i + 0.5) * PI / 4096);
|
||||
double transform = -cos(PI / 2048 * (i + 2048 + 0.5 + 1024) * 0.5);
|
||||
double window = sin((i + 2048 + 0.5) * PI / 4096);
|
||||
reference[i] =
|
||||
(float)(256 * pow(10.0, 5.0) * (2.0 / 2048) / 32768 * transform * window);
|
||||
}
|
||||
|
||||
@@ -356,8 +356,12 @@ static void standard_test(void)
|
||||
"uncoded standard frame");
|
||||
zero_frame(&f, n);
|
||||
check(used == sizeof(packet), "standard packet released");
|
||||
/* the drain gives the last frame's overlap tail, once */
|
||||
check(!rwma_decode_packet(d, NULL, 0, &used, &f) && f.samples == n,
|
||||
"standard drain gives the overlap tail");
|
||||
zero_frame(&f, n);
|
||||
check(!rwma_decode_packet(d, NULL, 0, &used, &f) && !f.samples,
|
||||
"standard drain adds no invented tail");
|
||||
"standard drain gives it once");
|
||||
rwma_free(d);
|
||||
}
|
||||
cfg.codec = RWMA_V2;
|
||||
|
||||
Reference in new issue
Block a user