@@ -52,6 +52,11 @@ MediaPanel::MediaPanel(QWidget *parent) :
52
52
m_PlaybackTimer = new QTimer (this );
53
53
m_PlaybackTimer->setInterval (10 );
54
54
connect (m_PlaybackTimer, &QTimer::timeout, this , &MediaPanel::TimerUpdate);
55
+
56
+ m_audioSink = nullptr ;
57
+
58
+ m_audioDevice = new MediaAudioMixer (this );
59
+ m_audioDevice->SetMediaInstances (&m_mediaInstances);
55
60
}
56
61
57
62
MediaPanel::~MediaPanel ()
@@ -371,15 +376,6 @@ void MediaPanel::OpenMediaInstance(si::Object *o)
371
376
372
377
void MediaPanel::Play (bool e)
373
378
{
374
- {
375
- // No matter what, stop any current audio
376
- std::vector<QAudioSink*> copy = m_audioSinks;
377
- for (auto it=copy.cbegin (); it!=copy.cend (); it++) {
378
- auto o = *it;
379
- o->stop ();
380
- }
381
- }
382
-
383
379
if (e) {
384
380
bool has_video = false ;
385
381
bool has_audio = false ;
@@ -394,22 +390,23 @@ void MediaPanel::Play(bool e)
394
390
auto output_dev = QAudioDevice (QMediaDevices::defaultAudioOutput ());
395
391
auto fmt = output_dev.preferredFormat ();
396
392
397
- ClearAudioSinks ();
398
-
399
- for (auto it=m_mediaInstances.cbegin (); it!=m_mediaInstances.cend (); it++) {
400
- auto m = *it;
393
+ // Require float output (makes our lives easier)
394
+ fmt.setSampleFormat (QAudioFormat::Float);
395
+
396
+ for (size_t i = 0 ; i < m_mediaInstances.size (); i++) {
397
+ auto m = m_mediaInstances[i];
401
398
402
399
m->ResetEOF ();
403
400
404
401
if (m->codec_type () == AVMEDIA_TYPE_VIDEO) {
405
402
has_video = true ;
406
403
} else if (m->codec_type () == AVMEDIA_TYPE_AUDIO) {
407
404
if (m_PlaybackOffset < (m->GetDuration () + m->GetStartOffset ())) {
408
- if (m->StartPlayingAudio (output_dev, fmt)) {
409
- auto out = new QAudioSink (output_dev, fmt, this );
410
- out->setVolume (m->GetVolume ());
411
- out->start (m);
412
- m_audioSinks.push_back (out);
405
+ if (m->SetUpResampleContext ( fmt)) {
406
+ // auto out = new QAudioSink(output_dev, fmt, this);
407
+ // out->setVolume(m->GetVolume());
408
+ // out->start(m);
409
+ // m_audioSinks.push_back(out);
413
410
has_audio = true ;
414
411
}
415
412
} else {
@@ -418,12 +415,28 @@ void MediaPanel::Play(bool e)
418
415
}
419
416
}
420
417
418
+ if (has_audio) {
419
+ m_audioDevice->SetAudioFormat (fmt);
420
+ m_audioDevice->open (QIODevice::ReadOnly);
421
+ m_audioDevice->SeekInSeconds (GetSecondsFromSlider ());
422
+
423
+ m_audioSink = new QAudioSink (output_dev, fmt, this );
424
+ m_audioSink->start (m_audioDevice);
425
+ }
426
+
421
427
m_PlaybackStart = QDateTime::currentMSecsSinceEpoch ();
422
428
m_PlaybackTimer->start ();
423
429
m_PlayBtn->setText (" Pause" );
424
430
} else {
425
431
m_PlayBtn->setText (" Play" );
426
432
m_PlaybackTimer->stop ();
433
+
434
+ if (m_audioSink) {
435
+ m_audioDevice->close ();
436
+ m_audioSink->stop ();
437
+ m_audioSink->deleteLater ();
438
+ m_audioSink = nullptr ;
439
+ }
427
440
}
428
441
m_PlayBtn->setChecked (e);
429
442
}
@@ -452,7 +465,6 @@ void MediaPanel::TimerUpdate()
452
465
}
453
466
454
467
if (all_eof) {
455
- ClearAudioSinks ();
456
468
Play (false );
457
469
m_PlayheadSlider->setValue (m_PlayheadSlider->maximum ());
458
470
}
@@ -507,26 +519,6 @@ void MediaPanel::LabelContextMenuTriggered(const QPoint &pos)
507
519
m.exec (static_cast <QWidget*>(sender ())->mapToGlobal (pos));
508
520
}
509
521
510
- void MediaPanel::ClearAudioSinks ()
511
- {
512
- if (m_audioSinks.size () != 0 ) {
513
- for (auto s : m_audioSinks)
514
- delete s;
515
-
516
- m_audioSinks.clear ();
517
- }
518
- }
519
-
520
- qint64 MediaInstance::readData (char *data, qint64 maxSize)
521
- {
522
- return ReadAudio (data, maxSize);
523
- }
524
-
525
- qint64 MediaInstance::writeData (const char *data, qint64 maxSize)
526
- {
527
- return -1 ;
528
- }
529
-
530
522
ClickableSlider::ClickableSlider (Qt::Orientation orientation, QWidget *parent) :
531
523
QSlider(orientation, parent)
532
524
{
@@ -547,6 +539,7 @@ void ClickableSlider::mousePressEvent(QMouseEvent *e)
547
539
}
548
540
549
541
MediaInstance::MediaInstance (QObject *parent) :
542
+ QObject(parent),
550
543
m_FmtCtx(nullptr ),
551
544
m_Packet(nullptr ),
552
545
m_CodecCtx(nullptr ),
@@ -557,7 +550,6 @@ MediaInstance::MediaInstance(QObject *parent) :
557
550
m_IoCtx(nullptr ),
558
551
m_startOffset(0 .0f )
559
552
{
560
- this ->open (QIODevice::ReadOnly);
561
553
}
562
554
563
555
void MediaInstance::Open (const si::bytearray &buf)
@@ -681,7 +673,7 @@ void MediaInstance::Close()
681
673
m_Data.Close ();
682
674
}
683
675
684
- bool MediaInstance::StartPlayingAudio ( const QAudioDevice &output_dev, const QAudioFormat &fmt)
676
+ bool MediaInstance::SetUpResampleContext ( const QAudioFormat &fmt)
685
677
{
686
678
if (m_SwrCtx) {
687
679
swr_free (&m_SwrCtx);
@@ -724,6 +716,8 @@ bool MediaInstance::StartPlayingAudio(const QAudioDevice &output_dev, const QAud
724
716
0 , nullptr );
725
717
if (r < 0 ) {
726
718
qCritical () << " Failed to alloc swr ctx:" << r;
719
+ return false ;
720
+ }
727
721
#else
728
722
m_SwrCtx = swr_alloc_set_opts (nullptr ,
729
723
av_get_default_channel_layout (fmt.channelCount ()),
@@ -735,19 +729,19 @@ bool MediaInstance::StartPlayingAudio(const QAudioDevice &output_dev, const QAud
735
729
0 , nullptr );
736
730
if (!m_SwrCtx) {
737
731
qCritical () << " Failed to alloc swr ctx" ;
732
+ return false ;
733
+ }
738
734
#endif
739
- } else {
740
- if (swr_init (m_SwrCtx) < 0 ) {
741
- qCritical () << " Failed to init swr ctx" ;
742
- } else {
743
- m_AudioFlushed = false ;
744
- m_AudioBuffer.clear ();
745
735
746
- return true ;
747
- }
736
+ if (swr_init (m_SwrCtx) < 0 ) {
737
+ qCritical () << " Failed to init swr ctx" ;
738
+ return false ;
748
739
}
749
740
750
- return false ;
741
+ m_AudioFlushed = false ;
742
+ m_AudioBuffer.clear ();
743
+
744
+ return true ;
751
745
}
752
746
753
747
void MediaInstance::Seek (float seconds)
@@ -788,3 +782,74 @@ void MediaInstance::SetVirtualTime(float f)
788
782
{
789
783
m_virtualPosition = f - m_startOffset;
790
784
}
785
+
786
+ MediaAudioMixer::MediaAudioMixer (QObject *parent) :
787
+ QIODevice(parent)
788
+ {
789
+ m_mediaInstances = nullptr ;
790
+ }
791
+
792
+ void MediaAudioMixer::SeekInSeconds (float f)
793
+ {
794
+ seek (m_audioFormat.bytesForDuration (f * 1000000 ));
795
+ }
796
+
797
+ qint64 MediaAudioMixer::readData (char *data, qint64 maxSize)
798
+ {
799
+ if (!m_mediaInstances) {
800
+ return 0 ;
801
+ }
802
+
803
+ // Media instances should be set to same sample rate and channel count as output, but we may need to convert format
804
+ float *output = reinterpret_cast <float *>(data);
805
+
806
+ qint64 maxSamples = maxSize / m_audioFormat.bytesPerSample ();
807
+
808
+ float *tmp = new float [maxSamples];
809
+
810
+ qint64 touchedBytes = 0 ;
811
+
812
+ for (auto it = m_mediaInstances->cbegin (); it != m_mediaInstances->cend (); it++) {
813
+ auto m = *it;
814
+
815
+ if (m->codec_type () == AVMEDIA_TYPE_AUDIO) {
816
+ qint64 thisRead = m->ReadAudio (reinterpret_cast <char *>(tmp), maxSize);
817
+ if (thisRead > touchedBytes) {
818
+ memset (data + touchedBytes, 0 , thisRead - touchedBytes);
819
+ touchedBytes = thisRead;
820
+ }
821
+
822
+ // TODO: Optimize with SSE and NEON
823
+ qint64 thisSamples = thisRead / m_audioFormat.bytesPerSample ();
824
+ for (qint64 j = 0 ; j < thisSamples; j++) {
825
+ output[j] += tmp[j] * m->GetVolume ();
826
+ }
827
+ }
828
+ }
829
+
830
+ delete [] tmp;
831
+
832
+ return touchedBytes;
833
+ }
834
+
835
+ qint64 MediaAudioMixer::writeData (const char *data, qint64 maxSize)
836
+ {
837
+ return -1 ;
838
+ }
839
+
840
+ qint64 MediaAudioMixer::size () const
841
+ {
842
+ if (!m_mediaInstances) {
843
+ return 0 ;
844
+ }
845
+
846
+ // Calculate maximum duration in seconds
847
+ float maxLength = 0 ;
848
+ for (auto it = m_mediaInstances->cbegin (); it != m_mediaInstances->cend (); it++) {
849
+ auto m = *it;
850
+ maxLength = qMax (maxLength, m->GetDuration () + m->GetStartOffset ());
851
+ }
852
+
853
+ // Convert seconds to bytes in the output format
854
+ return m_audioFormat.bytesForDuration (maxLength * 1000000 );
855
+ }
0 commit comments