Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

似乎解决了"no data in list for 1e8 times access"这个问题 #38

Conversation

pzhlkj6612
Copy link
Member

@pzhlkj6612 pzhlkj6612 commented Nov 5, 2019

#23 有关。

我并没有研究与 SDL 相关的代码,仅仅是对逻辑进行了一些分析,尝试直接跳过对tryTimesAGStatus的处理,返回-1

做测试,快速切换、重新播放歌曲,就暂时没有出现卡死的情况了。

已在 Windows 10 、 Ubuntu 18.04 和 macOS 10.14 下做了测试。

这个 PR 需要继续讨论,其中的道理可能也需要学习 SDL 后才能搞明白吧。


上述内容与提交 d05c494 有关(我又一次弄坏了 git 历史)。

@pzhlkj6612
Copy link
Member Author

pzhlkj6612 commented Nov 5, 2019

@pzhlkj6612
Copy link
Member Author

pzhlkj6612 commented Nov 10, 2019

本 PR 所修改代码所在的逻辑,在PlayThread::generateAudioDataLoop()中关于判定音乐到达尾部的逻辑看来,似乎有些多余。

https://github.com/Beslyric-for-X/Beslyric-for-X/blob/0dee64a8834f1ba294b12f99fee97acc438ee500/Entities/MusicPlayer/musicPlayer.cpp#L170-L196

https://github.com/Beslyric-for-X/Beslyric-for-X/blob/0dee64a8834f1ba294b12f99fee97acc438ee500/Entities/MusicPlayer/musicPlayer.cpp#L568-L576

在理想的情况下,在尾部(或附近)PlayThread::packet_queue_get()拿到的包的时间戳应该会保持不变,这时res == lastRes就表明音乐已到达尾部。if (tryTimes >= 100000000LL)的判断正是为了这个需求服务,这个不难理解。

但实际情况是,由于我现在还不知道的原因,PlayThread::packet_queue_get()在音乐开头也会多次拿到相同时间戳的包,有时甚至会“卡死”在开头,导致 #23 描述的问题出现;另外,在 seek 后同样会拿到许多相同时间戳的包。这时,if (tryTimes >= 100000000LL)的判断就显得不科学了,毕竟每一次获得相同时间戳包的数量并没有规律。

我做了测试,仅依赖if (tryTimes >= 100000000LL)来判断音乐是否到达尾部的确是可行的,但是,如果拿当前时间音乐总时长来作比较,似乎显得更加稳妥,所以拿掉整个逻辑可能是一个不错的选择。


不过,MS->audio_clock = res / 1000;是需要保留的,不然获取当前时间的功能会随之瘫痪。

@pzhlkj6612
Copy link
Member Author

更加稳妥的关于判定音乐到达尾部的方法:

if(m_MS.audioq.first_pkt == nullptr){
    qDebug() << "ending reached";
    AGStatus = AGS_FINISH;
    isEndByForce = false;
    break;
}

截至目前,测试的效果非常好,比用时间差判断不知道高到哪里去了。

@pzhlkj6612 pzhlkj6612 force-pushed the no_data_in_list_for_1e8_times_access branch 2 times, most recently from a589dae to 3d6d510 Compare November 11, 2019 09:51
@pzhlkj6612
Copy link
Member Author

提交 3d6d510 就是可以用的,而且逻辑简明的版本了。

@pzhlkj6612 pzhlkj6612 closed this Nov 11, 2019
@pzhlkj6612
Copy link
Member Author

找到一个长得有些不一样的audio_decode_frame的实现:

https://github.com/what951006/FFmpeg-Player/blob/9301296630caeb07915b009dd66982a19c039e84/FFmpegPlayer.cpp#L106-L172

@BensonLaur
Copy link
Member

这个项目的播放逻辑是参考复制一个酷狗demo的,https://github.com/BensonLaur/KuGouDemo

@pzhlkj6612
Copy link
Member Author

我又来打扰 watching 本项目的各位了。看来被关闭的 PR 不能自动更新,现在我提交了一个更改,所以要把 PR 打开。等最后一起修复(提交总的 PR )的时候我会再来关闭。抱歉。

@pzhlkj6612 pzhlkj6612 reopened this Nov 13, 2019
@pzhlkj6612
Copy link
Member Author

pzhlkj6612 commented Nov 15, 2019

这个项目的播放逻辑是参考复制一个酷狗demo的,https://github.com/BensonLaur/KuGouDemo

好的,谢谢。


c95866e

我发现了一个新问题,在packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)方法中,当g_isQuit == false时,返回值不可能小于 0 :

https://github.com/Beslyric-for-X/Beslyric-for-X/blob/0dee64a8834f1ba294b12f99fee97acc438ee500/Entities/MusicPlayer/musicPlayer.cpp#L75-L111

这会使得PlayThread::audio_decode_frame(mediaState* MS, uint8_t* audio_buf)方法中的判断失去作用,从而可能让一个仅被初始化的AVPacket包进入接下来的处理流程:

https://github.com/Beslyric-for-X/Beslyric-for-X/blob/0dee64a8834f1ba294b12f99fee97acc438ee500/Entities/MusicPlayer/musicPlayer.cpp#L160-L163

这或许与no data in list for 1e8 times access这个问题有关。因为该AVPacket包也不满足audio_pkt_size > 0,这样本次循环就无法结束。


但是做进一步分析能发现,这个循环的开头会使用packet_queue_get()方法重新获得包。正常情况下,由于packet_queue_put()会尽快向队列放入有效的包,所以即使packet_queue_get()<0tryTimes增加,也不该重复太多遍。这我现在还没弄明白。


我发现本问题在 Windows 上似乎更容易复现,但按照本 PR 进行修改后,暂时还没有再次出现过。

另外,总体来说,本问题的确很难复现,所以作者测试后才会这样说: #23 (comment) 。这也是我很疑惑的地方。

@pzhlkj6612
Copy link
Member Author

pzhlkj6612 commented Nov 16, 2019

流水账:

由于“信号——信号槽”的机制导致的滞后效果,以下的日志内容中BottomWidget::onAudioPlayBottomWidget::onAudioPauseBottomWidget::onAudioFinished信号槽内的输出不具有参考性。

根据观察,在没有修改代码的状态下,no data in list for 1e8 times access总是出现在以下调用序列之后:

//BottomWidget::positionChanged => audioOriginalPos= 2011  sliderSong->value()= 8
//BottomWidget::positionChanged => audioOriginalPos= 2115  sliderSong->value()= 9 // Audio is playing
BottomWidget::reloadMusic(QString)
MusicPlayer::stop()
//no data in list for 1e8 times access
//no data in list for 1e8 times access
//...

为了找出输出MusicPlayer::stop()之后程序在干嘛,于是加上更多的输出:

void MusicPlayer::stop()
{
    qDebug()<<"void MusicPlayer::stop() bIsLock="<<bIsLock;

    playThread->setAGStatus(AGS_FINISH);

+   qDebug()<<"post playThread->setAGStatus(AGS_FINISH);";

    //停止音乐必须保证线程真的退出(否则 playThread->bIsDeviceInit 可能判断成立,而实际线程还没退出,导致下一次 播放 playThread->playDevice() 没能及时起作用)
    while(playThread->isRunning())
        _millisecondSleep(10); //等待结束

+   qDebug()<<"post !playThread->isRunning()";

    if(bIsLock)
    {
        audioFinishedToThreadExitMutex.unlock();
        bIsLock = false;
    }
    //emit sig_playThreadFinished();
}

现在,正常的重新载入音乐的序列为:

//BottomWidget::positionChanged => audioOriginalPos= 1541  sliderSong->value()= 5
//BottomWidget::positionChanged => audioOriginalPos= 1645  sliderSong->value()= 5// Audio is playing
void  BottomWidget::reloadMusic(QString) // from MainWidget::OnPlayNewMusicAndLyric(QString, QString)
void MusicPlayer::stop()             // from BottomWidget::reloadMusic(QString)
post playThread->setAGStatus(AGS_FINISH);
&PlayThread::audioFinish         // emitted from PlayThread::ReleaseAll() in PlayThread::run()
&PlayThread::finished            // emitted because PlayThread is end
post !playThread->isRunning()
void BottomWidget::play()                // from MainWidget::OnPlayNewMusicAndLyric(QString, QString)
void MusicPlayer::play()             //from BottomWidget::play()
BottomWidget::positionChanged(qint64)
void BottomWidget::onAudioFinished(bool)
BottomWidget::positionChanged(qint64)
// Audio info
&PlayThread::audioPlay   // in PlayThread::run()
&PlayThread::audioPause
//seek successful     from  0  to :
&PlayThread::audioPlay
//to  0
void BottomWidget::onAudioPlay()
void BottomWidget::onAudioPause()
void BottomWidget::onAudioPlay()
//BottomWidget::positionChanged => audioOriginalPos= 182  sliderSong->value()= 0 // Audio is playing
//BottomWidget::positionChanged => audioOriginalPos= 287  sliderSong->value()= 0

如果出问题了,就有以下两种情况(均有两次很接近的BottomWidget::reloadMusic(QString)调用):

  1. 输出no data in list for 1e8 times access

没有音乐播放,界面卡死。

//BottomWidget::positionChanged => audioOriginalPos= 1541  sliderSong->value()= 5
//BottomWidget::positionChanged => audioOriginalPos= 1645  sliderSong->value()= 5// Audio is playing
void  BottomWidget::reloadMusic(QString) // <------
void MusicPlayer::stop()                 // <------
post playThread->setAGStatus(AGS_FINISH);
&PlayThread::audioFinish
&PlayThread::finished
post !playThread->isRunning()
void BottomWidget::play()
void MusicPlayer::play()
BottomWidget::positionChanged(qint64)
void BottomWidget::onAudioFinished(bool)
// Audio info
BottomWidget::positionChanged(qint64)
&PlayThread::audioPlay                   // 在此之后
void  BottomWidget::reloadMusic(QString) // <------
void MusicPlayer::stop()                 // <------
post playThread->setAGStatus(AGS_FINISH);
//no data in list for 1e8 times access
//no data in list for 1e8 times access
  1. 不输出no data in list for 1e8 times access

音乐正常播放,界面卡死,不输出no data in list for 1e8 times access。这个状态下,第一遍音乐播放完后,还会再播放数遍,界面才恢复响应。

//BottomWidget::positionChanged => audioOriginalPos= 1541  sliderSong->value()= 5
//BottomWidget::positionChanged => audioOriginalPos= 1645  sliderSong->value()= 5// Audio is playing
void  BottomWidget::reloadMusic(QString) // <------
void MusicPlayer::stop()                 // <------
post playThread->setAGStatus(AGS_FINISH);
&PlayThread::audioFinish
&PlayThread::finished
post !playThread->isRunning()
void BottomWidget::play()
void MusicPlayer::play()
void  BottomWidget::reloadMusic(QString) // <------
void MusicPlayer::stop()                 // <------
// Audio info
&PlayThread::audioPlay                   // 在此之前
&PlayThread::audioPause
post playThread->setAGStatus(AGS_FINISH);
//seek successful     from  0  to :
&PlayThread::audioPlay
//to  0
// 界面卡死

只有post playThread->setAGStatus(AGS_FINISH);没有后面的post !playThread->isRunning(),表示程序卡在了while(playThread->isRunning()),由于MusicPlayer::stop()是由界面调用的,所以界面卡死。

以上两种情况的主要差别是,MusicPlayer::stop()(即BottomWidget::reloadMusic(QString))的调用是在&PlayThread::audioPlay信号发出前还是发出后。&PlayThread::audioPlay信号来自PlayThread::playDevice()


重现一部分no data in list for 1e8 times access时的现象,可以做如下处理:

  1. 注释seekPos(10),因为现在的逻辑会在 seek 后发送&PlayThread::audioPlay信号;
  2. MusicPlayer类中添加:
+   static int chance = 1; //让第一次播放正常
    connect(playThread, &PlayThread::audioPlay,[=](){
+       if(chance > 0){
+           chance--;
+       }else{
+           stop();
+       }

        qDebug()<<"&PlayThread::audioPlay bIsLock="<<bIsLock;

        emit audioPlay();
    });
  1. 双击列表中的项目播放音乐,然后等音乐播放后,再双击播放音乐, boom !

不过,由于 lambda 表达式作为信号槽的特性,这里调用stop()后不会卡死界面。

@pzhlkj6612
Copy link
Member Author

这种分析太痛苦了,而且效果不好……但是也能看出,现在的 播放逻辑 和 界面与播放交互逻辑 的设计似乎的确是有问题(互斥锁的使用,线程状态的判断等等),所以重构可能才是更好的方法。

我这边已经把sliderSongMusicPlayer内所需的逻辑做了修改(已实现完美的 seek 效果),现在正考虑新写一个MusicPlayer_2,这样最后的 PR 就不包含对MusicPlayerPlayerThread的修改,看着简洁一些。

@BensonLaur
Copy link
Member

这个由于这边也是对ffmpeg理解很不够,所以照葫芦画瓢的拿了一段播放代码过来用,遇到问题修修补补,比如那个检测一亿次的部分也是遇到问题就尝试补一补试图临时解决问题,设计确实不大好

@pzhlkj6612 pzhlkj6612 force-pushed the no_data_in_list_for_1e8_times_access branch from c61c84c to 8524ad5 Compare December 10, 2019 12:00
@pzhlkj6612 pzhlkj6612 force-pushed the no_data_in_list_for_1e8_times_access branch from 8524ad5 to e6f06df Compare January 7, 2020 11:06
@pzhlkj6612 pzhlkj6612 closed this Jan 21, 2020
@pzhlkj6612 pzhlkj6612 deleted the no_data_in_list_for_1e8_times_access branch January 21, 2020 15:25
@pzhlkj6612 pzhlkj6612 added _Refactor the player 整个播放逻辑需要重做。 idea Discussing new idea labels Feb 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
abandoned idea Discussing new idea _Refactor the player 整个播放逻辑需要重做。
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants