@@ -482,6 +482,139 @@ TEST_CASE( "redistribute_samples_per_frame", "[libopenshot][framemapper]" ) {
482482 r.Close ();
483483}
484484
485+ TEST_CASE ( " Distribute samples" , " [libopenshot][framemapper]" ) {
486+ // This test verifies that audio data can be redistributed correctly
487+ // between common and uncommon frame rates
488+ int sample_rate = 48000 ;
489+ int channels = 2 ;
490+ int num_seconds = 1 ;
491+
492+ // Source frame rates (varies the # of samples per frame)
493+ std::vector<openshot::Fraction> rates = {
494+ openshot::Fraction (30 ,1 ),
495+ openshot::Fraction (24 ,1 ) ,
496+ openshot::Fraction (119 ,4 ),
497+ openshot::Fraction (30000 ,1001 )
498+ };
499+
500+ for (auto & frame_rate : rates) {
501+ // Init sin wave variables
502+ int OFFSET = 0 ;
503+ float AMPLITUDE = 0.75 ;
504+ int NUM_SAMPLES = 100 ;
505+ double angle = 0.0 ;
506+
507+ // Create cache object to hold test frames
508+ openshot::CacheMemory cache;
509+
510+ // Let's create some test frames
511+ for (int64_t frame_number = 1 ; frame_number <= (frame_rate.ToFloat () * num_seconds * 2 ); ++frame_number) {
512+ // Create blank frame (with specific frame #, samples, and channels)
513+ int sample_count = openshot::Frame::GetSamplesPerFrame (frame_number, frame_rate, sample_rate, channels);
514+ auto f = std::make_shared<openshot::Frame>(frame_number, sample_count, channels);
515+ f->SampleRate (sample_rate);
516+
517+ // Create test samples with sin wave (predictable values)
518+ float *audio_buffer = new float [sample_count * 2 ];
519+ for (int sample_number = 0 ; sample_number < sample_count; sample_number++) {
520+ // Calculate sin wave
521+ float sample_value = float (AMPLITUDE * sin (angle) + OFFSET);
522+ audio_buffer[sample_number] = abs (sample_value);
523+ angle += (2 * M_PI) / NUM_SAMPLES;
524+ }
525+
526+ // Add custom audio samples to Frame (bool replaceSamples, int destChannel, int destStartSample, const float* source,
527+ f->AddAudio (true , 0 , 0 , audio_buffer, sample_count, 1.0 ); // add channel 1
528+ f->AddAudio (true , 1 , 0 , audio_buffer, sample_count, 1.0 ); // add channel 2
529+
530+ // Add test frame to dummy reader
531+ cache.Add (f);
532+
533+ delete[] audio_buffer;
534+ }
535+
536+ openshot::DummyReader r (frame_rate, 1920 , 1080 , sample_rate, channels, 30.0 , &cache);
537+ r.Open ();
538+
539+ // Target frame rates
540+ std::vector<openshot::Fraction> mapped_rates = {
541+ openshot::Fraction (30 ,1 ),
542+ openshot::Fraction (24 ,1 ),
543+ openshot::Fraction (119 ,4 ),
544+ openshot::Fraction (30000 ,1001 )
545+ };
546+ for (auto &mapped_rate : mapped_rates) {
547+ // Reset SIN wave
548+ angle = 0.0 ;
549+
550+ // Map to different fps
551+ FrameMapper map (&r, mapped_rate, PULLDOWN_NONE, sample_rate, channels, LAYOUT_STEREO);
552+ map.info .has_audio = true ;
553+ map.Open ();
554+
555+ // Loop through samples, and verify FrameMapper didn't mess up individual sample values
556+ int num_samples = 0 ;
557+ for (int frame_index = 1 ; frame_index <= (map.info .fps .ToInt () * num_seconds); frame_index++) {
558+ int sample_count = map.GetFrame (frame_index)->GetAudioSamplesCount ();
559+ for (int sample_index = 0 ; sample_index < sample_count; sample_index++) {
560+
561+ // Calculate sin wave
562+ float predicted_value = abs (float (AMPLITUDE * sin (angle) + OFFSET));
563+ angle += (2 * M_PI) / NUM_SAMPLES;
564+
565+ // Verify each mapped sample value is correct (after being redistributed by the FrameMapper)
566+ float mapped_value = map.GetFrame (frame_index)->GetAudioSample (0 , sample_index, 1.0 );
567+ CHECK (predicted_value == Approx (mapped_value).margin (0.001 ));
568+ }
569+ // Increment sample value
570+ num_samples += map.GetFrame (frame_index)->GetAudioSamplesCount ();
571+ }
572+
573+ float clip_position = 3.77 ;
574+ int starting_clip_frame = round (clip_position * map.info .fps .ToFloat ()) + 1 ;
575+
576+ // Create Timeline (same specs as reader)
577+ Timeline t1 (map.info .width , map.info .height , map.info .fps , map.info .sample_rate , map.info .channels ,
578+ map.info .channel_layout );
579+
580+ Clip c1;
581+ c1.Reader (&map);
582+ c1.Layer (1 );
583+ c1.Position (clip_position);
584+ c1.Start (0.0 );
585+ c1.End (10.0 );
586+
587+ // Add clips
588+ t1.AddClip (&c1);
589+ t1.Open ();
590+
591+ // Reset SIN wave
592+ angle = 0.0 ;
593+
594+ for (int frame_index = starting_clip_frame; frame_index < (starting_clip_frame + (t1.info .fps .ToFloat () * num_seconds)); frame_index++) {
595+ for (int sample_index = 0 ; sample_index < t1.GetFrame (frame_index)->GetAudioSamplesCount (); sample_index++) {
596+ // Calculate sin wave
597+ float predicted_value = abs (float (AMPLITUDE * sin (angle) + OFFSET));
598+ angle += (2 * M_PI) / NUM_SAMPLES;
599+
600+ // Verify each mapped sample value is correct (after being redistributed by the FrameMapper)
601+ float timeline_value = t1.GetFrame (frame_index)->GetAudioSample (0 , sample_index, 1.0 );
602+ CHECK (predicted_value == Approx (timeline_value).margin (0.001 ));
603+ }
604+ }
605+
606+ // Close mapper
607+ map.Close ();
608+ t1.Close ();
609+ }
610+
611+ // Clean up reader
612+ r.Close ();
613+ cache.Clear ();
614+
615+ } // for rates
616+ }
617+
485618TEST_CASE ( " Json" , " [libopenshot][framemapper]" )
486619{
487620 DummyReader r (Fraction (30 ,1 ), 1280 , 720 , 48000 , 2 , 5.0 );
0 commit comments