mirror of
https://github.com/FelipePrado0/SpartanPort.git
synced 2026-11-07 10:45:59 -10:00
EN-US - Fixed words cut from the German narration in the opening cinematic: a byte pattern inside audio data was read as an FGH tag and reset the stream mid-speech - Fixed OFL headers being parsed on every frame; they are now read only at the start of a stream, and the gapless state resets with the decoder - Added an mp3-test case with a fake FGH signature inside an audio frame - Credited Sabari-Vasan-SM (#1091) --- PT-BR - Corrigidas as palavras cortadas da narração em alemão na cinemática de abertura: um padrão de bytes dentro dos dados de áudio era lido como tag FGH e reiniciava o stream no meio da fala - Corrigida a leitura de cabeçalhos OFL em todo quadro; agora eles só são lidos no início do stream, e o estado gapless é reiniciado junto com o decodificador - Adicionado um caso no mp3-test com uma assinatura FGH falsa dentro de um quadro de áudio - Creditado Sabari-Vasan-SM (#1091)
105 lines
5.3 KiB
C
105 lines
5.3 KiB
C
/* MP3 through libSceAjm: header/gapless parsing and decoding jobs (tests/data/sine.mp3:
|
|
* 0.5 s of 440 Hz, 44.1 kHz stereo, made with ffmpeg -c:a libmp3lame -id3v2_version 0; the
|
|
* encoder field of its tag frame is renamed from "Lavc63.1" to "LAME3.10", the only one the
|
|
* SDK reads). */
|
|
#undef NDEBUG /* the checks are asserts; release builds define NDEBUG */
|
|
#include "../src/runtime_ajm.c"
|
|
#include <assert.h>
|
|
|
|
uintptr_t runtime_lookup(const RuntimeExport *table, size_t count, const char *nid) { (void)table; (void)count; (void)nid; return 0; }
|
|
|
|
static unsigned char *load(const char *path, uint32_t *size) {
|
|
FILE *f=fopen(path,"rb");
|
|
assert(f);
|
|
static unsigned char data[1<<16];
|
|
*size=(uint32_t)fread(data,1,sizeof(data),f);
|
|
fclose(f);
|
|
return data;
|
|
}
|
|
|
|
static uint64_t frame_size_of(const unsigned char *data) {
|
|
Mp3Frame f; assert(runtime_mp3_parse(data,4,0,1,&f)==0); return f.frame_size;
|
|
}
|
|
|
|
/* One decoding job; returns the sideband result and fills stream/gapless. */
|
|
static int32_t run(uint32_t ctx, uint32_t instance, const void *in, uint64_t in_size, void *out, uint64_t out_size,
|
|
uint64_t flags, SidebandStream *stream, SidebandGapless *gapless) {
|
|
unsigned char batch[256], side[64]={0};
|
|
unsigned char *end=job_run(batch,instance,flags,(void *)in,in_size,out,out_size,side,sizeof(side),NULL);
|
|
uint32_t id;
|
|
assert(ajm_batch_start(ctx,batch,(uint32_t)(end-batch),0,NULL,&id)==0);
|
|
assert(ajm_batch_wait(ctx,id,0,NULL)==0);
|
|
SidebandResult r; memcpy(&r,side,8);
|
|
if (stream) memcpy(stream,side+8,16);
|
|
if (gapless) memcpy(gapless,side+24,8);
|
|
return r.result;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
uint32_t size;
|
|
const unsigned char *mp3=load(argc>1 ? argv[1] : "tests/data/sine.mp3",&size);
|
|
|
|
Mp3Frame frame;
|
|
assert(ajm_mp3_parse_frame(mp3,size,1,&frame)==0);
|
|
assert(frame.sample_rate==44100 && frame.num_channels==2 && frame.samples_per_channel==1152);
|
|
assert(frame.ofl_type==1 && frame.total_samples==22050 && frame.encoder_delay>1152);
|
|
assert(ajm_mp3_parse_frame((const unsigned char *)"\0\0\0\0",4,1,&frame)==ERR_INVALID_PARAMETER);
|
|
assert(ajm_mp3_parse_frame(mp3,3,1,&frame)==ERR_INVALID_PARAMETER);
|
|
|
|
uint32_t ctx, instance;
|
|
assert(ajm_initialize(0,&ctx)==0 && ajm_module_register(ctx,0,0)==0);
|
|
assert(ajm_instance_create(ctx,0,1|(FORMAT_S16<<7),&instance)==0 && ajm_instance_codec(instance)==0);
|
|
|
|
/* The whole stream in one multi-frame job: exactly the gapless samples come out. */
|
|
static int16_t pcm[44100*2];
|
|
SidebandStream stream; SidebandGapless gapless;
|
|
uint64_t multi=UINT64_C(1)<<12|UINT64_C(1)<<47|UINT64_C(1)<<45;
|
|
int32_t r=run(ctx,instance,mp3,size,pcm,sizeof(pcm),multi,&stream,&gapless);
|
|
assert((r & ~RESULT_PARTIAL_INPUT)==0);
|
|
assert(stream.output_written==22050*2*2 && stream.total_decoded_samples==22050);
|
|
assert(stream.input_consumed>0 && (uint32_t)stream.input_consumed<=size);
|
|
int16_t peak=0;
|
|
for (int i=0;i<22050*2;++i) if (pcm[i]>peak) peak=pcm[i];
|
|
/* Reference: ffmpeg -i sine.mp3 -f s16le gives 22050 samples, peak 2761, [0]=10, [1000]=-196
|
|
* (gapless trimming at the same sample; +-1 for float to s16 rounding). */
|
|
assert(peak>=2759 && peak<=2763 && abs(pcm[0]-10)<=1 && abs(pcm[1000]+196)<=1);
|
|
|
|
/* Reset, then a small output: the skipped tag frame fits, the next frame does not. */
|
|
unsigned char control[8];
|
|
unsigned char batch[128];
|
|
unsigned char *end=job_control(batch,instance,UINT64_C(1)<<13,control,0,control,sizeof(control),NULL);
|
|
uint32_t id; assert(ajm_batch_start(ctx,batch,(uint32_t)(end-batch),0,NULL,&id)==0 && ajm_batch_wait(ctx,id,0,NULL)==0);
|
|
int16_t small[16];
|
|
r=run(ctx,instance,mp3,size,small,sizeof(small),multi,&stream,NULL);
|
|
assert(r==RESULT_NOT_ENOUGH_ROOM && stream.output_written==0 && stream.input_consumed==(int32_t)frame_size_of(mp3));
|
|
|
|
/* False-positive FGH byte pattern in an audio frame must not trigger OFL or drop samples (#1091). */
|
|
static unsigned char fake_mp3[1<<16];
|
|
memcpy(fake_mp3,mp3,size);
|
|
/* In frame 10 (offset 4178, size 418), inject 0xB4 indicator with matching CRC-8 at byte 390. */
|
|
unsigned char fgh[10]={0xB4, 0x03, 0xE8, 0x00, 0x00, 0x56, 0x22, 0x00, 0x00, 0};
|
|
uint8_t crc=0xFF;
|
|
for (int i=0;i<9;++i) for (int j=7;j>=0;--j) crc=(uint8_t)(((crc>>7)&1)!=((fgh[i]>>j)&1) ? crc*2 : (crc*2)^0x45);
|
|
fgh[9]=crc;
|
|
memcpy(fake_mp3+4178+390,fgh,10);
|
|
Mp3Frame audio_frame;
|
|
assert(ajm_mp3_parse_frame(fake_mp3+4178,418,1,&audio_frame)==0);
|
|
assert(audio_frame.ofl_type==0 && audio_frame.encoder_delay==0);
|
|
|
|
memset(control,0,sizeof(control));
|
|
end=job_control(batch,instance,UINT64_C(1)<<13,control,0,control,sizeof(control),NULL);
|
|
assert(ajm_batch_start(ctx,batch,(uint32_t)(end-batch),0,NULL,&id)==0 && ajm_batch_wait(ctx,id,0,NULL)==0);
|
|
r=run(ctx,instance,fake_mp3,size,pcm,sizeof(pcm),multi,&stream,&gapless);
|
|
assert((r & ~RESULT_PARTIAL_INPUT)==0);
|
|
assert(stream.output_written==22050*2*2 && stream.total_decoded_samples==22050);
|
|
|
|
/* Garbage is a codec error result, not a crash. */
|
|
static const unsigned char junk[512]={0x12,0x34};
|
|
r=run(ctx,instance,junk,sizeof(junk),pcm,sizeof(pcm),UINT64_C(1)<<47,&stream,NULL);
|
|
assert(r & RESULT_CODEC_ERROR);
|
|
|
|
assert(ajm_instance_destroy(ctx,instance)==0 && ajm_finalize(ctx)==0);
|
|
puts("mp3 test: ok");
|
|
return 0;
|
|
}
|