playerdemowindow.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. #include "playerdemowindow.h"
  2. #include <QAudioDeviceInfo>
  3. #include <QAudioFormat>
  4. #include <QDebug>
  5. #include <QHBoxLayout>
  6. #include <QMessageBox>
  7. #include <QVBoxLayout>
  8. PlayerDemoWindow::PlayerDemoWindow(QWidget* parent)
  9. : QWidget(parent)
  10. {
  11. QVBoxLayout* layout = new QVBoxLayout(this);
  12. m_videoWidget = new OpenGLVideoWidget(this);
  13. layout->addWidget(m_videoWidget);
  14. // 进度条和时间
  15. QHBoxLayout* progressLayout = new QHBoxLayout();
  16. m_progressSlider = new QSlider(Qt::Horizontal, this);
  17. m_progressSlider->setRange(0, 1000);
  18. m_timeLabel = new QLabel("00:00:00 / 00:00:00", this);
  19. progressLayout->addWidget(m_progressSlider, 1);
  20. progressLayout->addWidget(m_timeLabel);
  21. layout->addLayout(progressLayout);
  22. // 倍速选择和播放按钮
  23. QHBoxLayout* controlLayout = new QHBoxLayout();
  24. m_playBtn = new QPushButton("播放", this);
  25. m_speedCombo = new QComboBox(this);
  26. m_speedCombo->addItem("0.5x", 0.5);
  27. m_speedCombo->addItem("1.0x", 1.0);
  28. m_speedCombo->addItem("1.5x", 1.5);
  29. m_speedCombo->addItem("2.0x", 2.0);
  30. controlLayout->addWidget(m_playBtn);
  31. controlLayout->addWidget(new QLabel("倍速:", this));
  32. controlLayout->addWidget(m_speedCombo);
  33. // 添加保持比例复选框
  34. m_keepAspectCheck = new QCheckBox("保持比例", this);
  35. m_keepAspectCheck->setChecked(true);
  36. controlLayout->addWidget(m_keepAspectCheck);
  37. layout->addLayout(controlLayout);
  38. m_speedCombo->setCurrentText("1.0x");
  39. connect(m_progressSlider, &QSlider::sliderPressed, [this]() { m_sliderPressed = true; });
  40. connect(m_progressSlider,
  41. &QSlider::sliderReleased,
  42. this,
  43. &PlayerDemoWindow::onProgressSliderReleased);
  44. connect(m_progressSlider, &QSlider::sliderMoved, this, &PlayerDemoWindow::onProgressSliderMoved);
  45. connect(m_speedCombo,
  46. QOverload<int>::of(&QComboBox::currentIndexChanged),
  47. this,
  48. &PlayerDemoWindow::onSpeedChanged);
  49. connect(m_playBtn, &QPushButton::clicked, this, &PlayerDemoWindow::onPlayClicked);
  50. connect(m_keepAspectCheck, &QCheckBox::toggled, this, [this](bool checked) {
  51. m_videoWidget->setKeepAspectRatio(checked);
  52. });
  53. }
  54. PlayerDemoWindow::~PlayerDemoWindow()
  55. {
  56. if (m_puller) {
  57. m_puller->stop();
  58. delete m_puller;
  59. }
  60. if (m_audioOutput) {
  61. m_audioOutput->stop();
  62. delete m_audioOutput;
  63. }
  64. if (m_swrCtx) {
  65. swr_free(&m_swrCtx);
  66. }
  67. if (m_swrBuffer) {
  68. av_free(m_swrBuffer);
  69. }
  70. }
  71. void PlayerDemoWindow::startPlay(const QString& url)
  72. {
  73. if (m_puller) {
  74. m_puller->stop();
  75. delete m_puller;
  76. m_puller = nullptr;
  77. }
  78. m_puller = new FFmpegVideoPuller();
  79. if (!m_puller->open(url, 300, 300)) {
  80. QMessageBox::critical(this, "错误", "无法打开流");
  81. return;
  82. }
  83. m_puller->setSpeed(m_speedCombo->currentData().toFloat());
  84. // 设置回调
  85. m_puller->setVideoRenderCallback([this](AVFrame* frame) {
  86. static bool firstFrame = true;
  87. if (firstFrame) {
  88. m_videoWidget->Open(frame->width, frame->height);
  89. firstFrame = false;
  90. }
  91. m_videoWidget->Render(frame);
  92. updateProgress(); // 每次渲染视频帧时刷新进度条
  93. });
  94. m_puller->setAudioPlayCallback([this](AVFrame* frame) {
  95. if (!m_audioOutput) {
  96. initAudio(frame);
  97. }
  98. playAudioFrame(frame);
  99. });
  100. m_puller->start();
  101. // 进度条初始化
  102. m_firstPts = m_puller->getFirstPts();
  103. m_lastPts = m_puller->getLastPts();
  104. m_duration = m_lastPts - m_firstPts;
  105. m_progressSlider->setValue(0);
  106. updateProgress();
  107. }
  108. void PlayerDemoWindow::onPlayClicked()
  109. {
  110. if (!m_puller)
  111. return;
  112. float curSpeed = m_puller->getSpeed();
  113. if (curSpeed > 0.01f) {
  114. m_puller->setSpeed(0.0f);
  115. m_playBtn->setText("播放");
  116. } else {
  117. float speed = m_speedCombo->currentData().toFloat();
  118. m_puller->setSpeed(speed);
  119. m_playBtn->setText("暂停");
  120. }
  121. }
  122. void PlayerDemoWindow::initAudio(const AVFrame* frame)
  123. {
  124. if (m_audioOutput) {
  125. m_audioOutput->stop();
  126. delete m_audioOutput;
  127. m_audioOutput = nullptr;
  128. }
  129. if (m_swrCtx) {
  130. swr_free(&m_swrCtx);
  131. m_swrCtx = nullptr;
  132. }
  133. if (m_swrBuffer) {
  134. av_free(m_swrBuffer);
  135. m_swrBuffer = nullptr;
  136. m_swrBufferSize = 0;
  137. }
  138. QAudioFormat fmt;
  139. m_audioSampleRate = frame->sample_rate;
  140. m_audioChannels = frame->ch_layout.nb_channels; // FFmpeg 7.x
  141. m_audioFormat = (AVSampleFormat) frame->format;
  142. fmt.setSampleRate(m_audioSampleRate);
  143. fmt.setChannelCount(m_audioChannels);
  144. fmt.setSampleSize(16); // 目标格式 S16
  145. fmt.setCodec("audio/pcm");
  146. fmt.setByteOrder(QAudioFormat::LittleEndian);
  147. fmt.setSampleType(QAudioFormat::SignedInt);
  148. QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
  149. if (!info.isFormatSupported(fmt)) {
  150. qWarning() << "音频格式不支持,尝试最近格式";
  151. fmt = info.nearestFormat(fmt);
  152. }
  153. m_audioOutput = new QAudioOutput(fmt, this);
  154. m_audioDevice = m_audioOutput->start();
  155. qDebug() << "initAudio, m_audioOutput=" << m_audioOutput << " m_audioDevice=" << m_audioDevice;
  156. if (m_audioOutput)
  157. qDebug() << "QAudioOutput error:" << m_audioOutput->error();
  158. AVChannelLayout out_ch_layout;
  159. av_channel_layout_default(&out_ch_layout, m_audioChannels);
  160. m_swrCtx = swr_alloc();
  161. if (!m_swrCtx) {
  162. qWarning() << "swr_alloc 失败";
  163. return;
  164. }
  165. if (swr_alloc_set_opts2(&m_swrCtx,
  166. &out_ch_layout,
  167. AV_SAMPLE_FMT_S16,
  168. m_audioSampleRate,
  169. &frame->ch_layout,
  170. (AVSampleFormat) frame->format,
  171. frame->sample_rate,
  172. 0,
  173. nullptr)
  174. < 0) {
  175. qWarning() << "swr_alloc_set_opts2 失败";
  176. swr_free(&m_swrCtx);
  177. return;
  178. }
  179. if (swr_init(m_swrCtx) < 0) {
  180. qWarning() << "swr_init 失败";
  181. swr_free(&m_swrCtx);
  182. m_swrCtx = nullptr;
  183. }
  184. av_channel_layout_uninit(&out_ch_layout);
  185. }
  186. void PlayerDemoWindow::playAudioFrame(AVFrame* frame)
  187. {
  188. qDebug() << "--------------" << m_audioOutput << m_audioDevice;
  189. if (!m_audioOutput || !m_audioDevice)
  190. return;
  191. int out_channels = m_audioChannels;
  192. int out_samples = frame->nb_samples;
  193. int out_bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
  194. // 目标缓冲区大小
  195. int needed_bufsize = av_samples_get_buffer_size(nullptr,
  196. out_channels,
  197. out_samples,
  198. AV_SAMPLE_FMT_S16,
  199. 1);
  200. if (!m_swrBuffer || m_swrBufferSize < needed_bufsize) {
  201. if (m_swrBuffer)
  202. av_free(m_swrBuffer);
  203. m_swrBuffer = (uint8_t*) av_malloc(needed_bufsize);
  204. m_swrBufferSize = needed_bufsize;
  205. }
  206. uint8_t* out[] = {m_swrBuffer};
  207. int converted = 0;
  208. // qDebug() << "playAudioFrame, m_audioOutput=" << m_audioOutput
  209. // << " m_audioDevice=" << m_audioDevice;
  210. if (m_swrCtx) {
  211. converted = swr_convert(m_swrCtx,
  212. out,
  213. out_samples,
  214. (const uint8_t**) frame->extended_data,
  215. frame->nb_samples);
  216. qDebug() << "swr_convert 返回" << converted;
  217. if (converted > 0) {
  218. int out_size = converted * out_channels * out_bytes_per_sample;
  219. qint64 written = m_audioDevice->write((const char*) m_swrBuffer, out_size);
  220. (void) written;
  221. // qDebug() << "写入音频数据, 大小=" << out_size << " 实际写入=" << written;
  222. }
  223. } else if (frame->format == AV_SAMPLE_FMT_S16) {
  224. // 直接写
  225. int dataSize = av_samples_get_buffer_size(nullptr,
  226. frame->ch_layout.nb_channels,
  227. frame->nb_samples,
  228. (AVSampleFormat) frame->format,
  229. 1);
  230. m_audioDevice->write((const char*) frame->data[0], dataSize);
  231. }
  232. }
  233. void PlayerDemoWindow::onProgressSliderMoved(int value)
  234. {
  235. m_sliderPressed = true;
  236. if (m_duration <= 0) {
  237. m_timeLabel->setText("00:00:00 / 00:00:00");
  238. return;
  239. }
  240. double seekPts = m_firstPts + (m_duration * value / 1000.0);
  241. QString cur = formatTime(seekPts - m_firstPts);
  242. QString total = formatTime(m_duration);
  243. m_timeLabel->setText(cur + " / " + total);
  244. }
  245. void PlayerDemoWindow::onProgressSliderReleased()
  246. {
  247. if (!m_puller || m_duration <= 0)
  248. return;
  249. int value = m_progressSlider->value();
  250. double seekPts = m_firstPts + (m_duration * value / 1000.0);
  251. if (seekPts < m_firstPts)
  252. seekPts = m_firstPts;
  253. if (seekPts > m_lastPts)
  254. seekPts = m_lastPts;
  255. m_puller->seekToPts(seekPts);
  256. m_sliderPressed = false;
  257. }
  258. void PlayerDemoWindow::onSpeedChanged(int index)
  259. {
  260. if (!m_puller)
  261. return;
  262. float speed = m_speedCombo->itemData(index).toFloat();
  263. m_puller->setSpeed(speed);
  264. }
  265. void PlayerDemoWindow::updateProgress()
  266. {
  267. if (!m_puller || m_sliderPressed)
  268. return;
  269. double duration = m_puller->getTotalDuration();
  270. if (duration > 0) {
  271. m_duration = duration;
  272. m_firstPts = 0;
  273. m_lastPts = duration;
  274. } else {
  275. m_firstPts = m_puller->getFirstPts();
  276. m_lastPts = m_puller->getLastPts();
  277. m_duration = m_lastPts - m_firstPts;
  278. }
  279. // 检查有效性
  280. if (m_firstPts < 0 || m_lastPts < 0 || m_duration <= 0) {
  281. m_progressSlider->setValue(0);
  282. m_timeLabel->setText("00:00:00 / 00:00:00");
  283. return;
  284. }
  285. double curPts = m_puller->getCurrentPts();
  286. if (curPts < m_firstPts)
  287. curPts = m_firstPts;
  288. if (curPts > m_lastPts)
  289. curPts = m_lastPts;
  290. int value = int((curPts - m_firstPts) / m_duration * 1000);
  291. m_progressSlider->setValue(value);
  292. QString cur = formatTime(curPts - m_firstPts);
  293. QString total = formatTime(m_duration);
  294. m_timeLabel->setText(cur + " / " + total);
  295. }
  296. QString PlayerDemoWindow::formatTime(double seconds) const
  297. {
  298. if (seconds < 0)
  299. seconds = 0;
  300. int sec = int(seconds + 0.5);
  301. int h = sec / 3600;
  302. int m = (sec % 3600) / 60;
  303. int s = sec % 60;
  304. return QString("%1:%2:%3")
  305. .arg(h, 2, 10, QChar('0'))
  306. .arg(m, 2, 10, QChar('0'))
  307. .arg(s, 2, 10, QChar('0'));
  308. }