/* *TODO: * if output is null or dummy, the use duration to wait */ void AudioThread::run() { DPTR_D(AudioThread); //No decoder or output. No audio output is ok, just display picture if (!d.dec || !d.dec->isAvailable() || !d.outputSet) return; resetState(); Q_ASSERT(d.clock != 0); AudioDecoder *dec = static_cast<AudioDecoder*>(d.dec); AudioOutput *ao = 0; // first() is not null even if list empty if (!d.outputSet->outputs().isEmpty()) ao = static_cast<AudioOutput*>(d.outputSet->outputs().first()); //TODO: not here d.init(); //TODO: bool need_sync in private class bool is_external_clock = d.clock->clockType() == AVClock::ExternalClock; Packet pkt; while (!d.stop) { processNextTask(); //TODO: why put it at the end of loop then playNextFrame() not work? if (tryPause()) { //DO NOT continue, or playNextFrame() will fail if (d.stop) break; //the queue is empty and may block. should setBlocking(false) wake up cond empty? } else { if (isPaused()) continue; } if (d.packets.isEmpty() && !d.stop) { d.stop = d.demux_end; } if (d.stop) { qDebug("audio thread stop before take packet"); break; } if (!pkt.isValid()) { pkt = d.packets.take(); //wait to dequeue } if (!pkt.isValid()) { qDebug("Invalid packet! flush audio codec context!!!!!!!! audio queue size=%d", d.packets.size()); dec->flush(); continue; } bool skip_render = pkt.pts < d.render_pts0; // audio has no key frame, skip rendering equals to skip decoding if (skip_render) { d.clock->updateValue(pkt.pts); /* * audio may be too fast than video if skip without sleep * a frame is about 20ms. sleep time must be << frame time */ qreal a_v = pkt.pts - d.clock->videoPts(); //qDebug("skip audio decode at %f/%f v=%f a-v=%f", pkt.pts, d.render_pts0, d.clock->videoPts(), a_v); if (a_v > 0) msleep(qMin((ulong)300, ulong(a_v*1000.0))); else msleep(2); pkt = Packet(); //mark invalid to take next continue; } d.render_pts0 = 0; if (is_external_clock) { d.delay = pkt.pts - d.clock->value(); /* *after seeking forward, a packet may be the old, v packet may be *the new packet, then the d.delay is very large, omit it. *TODO: 1. how to choose the value * 2. use last delay when seeking */ if (qAbs(d.delay) < 2.718) { if (d.delay < -kSyncThreshold) { //Speed up. drop frame? //continue; } while (d.delay > kSyncThreshold) { //Slow down //d.delay_cond.wait(&d.mutex, d.delay*1000); //replay may fail. why? //qDebug("~~~~~wating for %f msecs", d.delay*1000); usleep(kSyncThreshold * 1000000UL); if (d.stop) d.delay = 0; else d.delay -= kSyncThreshold; } if (d.delay > 0) usleep(d.delay * 1000000UL); } else { //when to drop off? if (d.delay > 0) { msleep(64); } else { //audio packet not cleaned up? continue; } } } else { d.clock->updateValue(pkt.pts); } //DO NOT decode and convert if ao is not available or mute! bool has_ao = ao && ao->isAvailable(); //if (!has_ao) {//do not decode? // TODO: move resampler to AudioFrame, like VideoFrame does if (has_ao && dec->resampler()) { if (dec->resampler()->speed() != ao->speed() || dec->resampler()->outAudioFormat() != ao->audioFormat()) { //resample later to ensure thread safe. TODO: test if (d.resample) { qDebug("decoder set speed: %.2f", ao->speed()); dec->resampler()->setOutAudioFormat(ao->audioFormat()); dec->resampler()->setSpeed(ao->speed()); dec->resampler()->prepare(); d.resample = false; } else { d.resample = true; } } } else { if (dec->resampler() && dec->resampler()->speed() != d.clock->speed()) { if (d.resample) { qDebug("decoder set speed: %.2f", d.clock->speed()); dec->resampler()->setSpeed(d.clock->speed()); dec->resampler()->prepare(); d.resample = false; } else { d.resample = true; } } } if (d.stop) { qDebug("audio thread stop before decode()"); break; } QMutexLocker locker(&d.mutex); Q_UNUSED(locker); if (!dec->decode(pkt.data)) { qWarning("Decode audio failed"); qreal dt = pkt.pts - d.last_pts; if (dt > 0.618 || dt < 0) { dt = 0; } //qDebug("sleep %f", dt); //TODO: avoid acummulative error. External clock? msleep((unsigned long)(dt*1000.0)); pkt = Packet(); d.last_pts = d.clock->value(); //not pkt.pts! the delay is updated! continue; } QByteArray decoded(dec->data()); int decodedSize = decoded.size(); int decodedPos = 0; qreal delay = 0; //AudioFormat.durationForBytes() calculates int type internally. not accurate AudioFormat &af = dec->resampler()->inAudioFormat(); qreal byte_rate = af.bytesPerSecond(); while (decodedSize > 0) { if (d.stop) { qDebug("audio thread stop after decode()"); break; } // TODO: set to format.bytesPerFrame()*1024? const int chunk = qMin(decodedSize, has_ao ? ao->bufferSize() : 1024*4);//int(max_len*byte_rate)); //AudioFormat.bytesForDuration const qreal chunk_delay = (qreal)chunk/(qreal)byte_rate; pkt.pts += chunk_delay; QByteArray decodedChunk(chunk, 0); //volume == 0 || mute if (has_ao) { //TODO: volume filter and other filters!!! if (!ao->isMute()) { decodedChunk = QByteArray::fromRawData(decoded.constData() + decodedPos, chunk); qreal vol = ao->volume(); if (vol != 1.0) { int len = decodedChunk.size()/ao->audioFormat().bytesPerSample(); switch (ao->audioFormat().sampleFormat()) { case AudioFormat::SampleFormat_Unsigned8: case AudioFormat::SampleFormat_Unsigned8Planar: { quint8 *data = (quint8*)decodedChunk.data(); //TODO: other format? for (int i = 0; i < len; data[i++] *= vol) {} } break; case AudioFormat::SampleFormat_Signed16: case AudioFormat::SampleFormat_Signed16Planar: { qint16 *data = (qint16*)decodedChunk.data(); //TODO: other format? for (int i = 0; i < len; data[i++] *= vol) {} } break; case AudioFormat::SampleFormat_Signed32: case AudioFormat::SampleFormat_Signed32Planar: { qint32 *data = (qint32*)decodedChunk.data(); //TODO: other format? for (int i = 0; i < len; data[i++] *= vol) {} } break; case AudioFormat::SampleFormat_Float: case AudioFormat::SampleFormat_FloatPlanar: { float *data = (float*)decodedChunk.data(); //TODO: other format? for (int i = 0; i < len; data[i++] *= vol) {} } break; case AudioFormat::SampleFormat_Double: case AudioFormat::SampleFormat_DoublePlanar: { double *data = (double*)decodedChunk.data(); //TODO: other format? for (int i = 0; i < len; data[i++] *= vol) {} } break; default: break; } } } ao->waitForNextBuffer(); ao->receiveData(decodedChunk, pkt.pts); ao->play(); d.clock->updateValue(ao->timestamp()); } else { d.clock->updateDelay(delay += chunk_delay); /* * why need this even if we add delay? and usleep sounds weird * the advantage is if no audio device, the play speed is ok too * So is portaudio blocking the thread when playing? */ static bool sWarn_no_ao = true; //FIXME: no warning when replay. warn only once if (sWarn_no_ao) { qDebug("Audio output not available! msleep(%lu)", (unsigned long)((qreal)chunk/(qreal)byte_rate * 1000)); sWarn_no_ao = false; } //TODO: avoid acummulative error. External clock? msleep((unsigned long)(chunk_delay * 1000.0)); } decodedPos += chunk; decodedSize -= chunk; } int undecoded = dec->undecodedSize(); if (undecoded > 0) { pkt.data.remove(0, pkt.data.size() - undecoded); } else { pkt = Packet(); } d.last_pts = d.clock->value(); //not pkt.pts! the delay is updated! } qDebug("Audio thread stops running..."); }
bool EventFilter::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched); AVPlayer *player = static_cast<AVPlayer*>(parent()); if (!player || !player->renderer() || !player->renderer()->widget()) return false; if (qobject_cast<QWidget*>(watched) != player->renderer()->widget()) { return false; } #ifndef QT_NO_DYNAMIC_CAST //dynamic_cast is defined as a macro to force a compile error if (player->renderer() != dynamic_cast<VideoRenderer*>(watched)) { // return false; } #endif QEvent::Type type = event->type(); switch (type) { case QEvent::KeyPress: { QKeyEvent *key_event = static_cast<QKeyEvent*>(event); int key = key_event->key(); Qt::KeyboardModifiers modifiers = key_event->modifiers(); switch (key) { case Qt::Key_C: //capture player->captureVideo(); break; case Qt::Key_N: //check playing? player->playNextFrame(); break; case Qt::Key_P: player->play(); break; case Qt::Key_Q: case Qt::Key_Escape: qApp->quit(); break; case Qt::Key_S: player->stop(); //check playing? break; case Qt::Key_Space: //check playing? qDebug("isPaused = %d", player->isPaused()); player->pause(!player->isPaused()); break; case Qt::Key_F: { //TODO: move to gui QWidget *w = qApp->activeWindow(); if (!w) return false; if (w->isFullScreen()) w->showNormal(); else w->showFullScreen(); } break; case Qt::Key_Up: { AudioOutput *ao = player->audio(); if (modifiers == Qt::ControlModifier) { qreal s = player->speed(); if (s < 1.4) s += 0.02; else s += 0.05; if (qAbs<qreal>(s-1.0) <= 0.01) s = 1.0; player->setSpeed(s); return true; } if (ao && ao->isAvailable()) { qreal v = player->audio()->volume(); if (v > 0.5) v += 0.1; else if (v > 0.1) v += 0.05; else v += 0.025; player->audio()->setVolume(v); qDebug("vol = %.3f", player->audio()->volume()); } } break; case Qt::Key_Down: { AudioOutput *ao = player->audio(); if (modifiers == Qt::ControlModifier) { qreal s = player->speed(); if (s < 1.4) s -= 0.02; else s -= 0.05; if (qAbs<qreal>(s-1.0) <= 0.01) s = 1.0; s = qMax<qreal>(s, 0.0); player->setSpeed(s); return true; } if (ao && ao->isAvailable()) { qreal v = player->audio()->volume(); if (v > 0.5) v -= 0.1; else if (v > 0.1) v -= 0.05; else v -= 0.025; player->audio()->setVolume(v); qDebug("vol = %.3f", player->audio()->volume()); } } break; case Qt::Key_O: { if (modifiers == Qt::ControlModifier) { //TODO: emit a signal so we can use custome dialogs? openLocalFile(); } else/* if (m == Qt::NoModifier) */{ emit showNextOSD(); } } break; case Qt::Key_Left: qDebug("<-"); player->seekBackward(); break; case Qt::Key_Right: qDebug("->"); player->seekForward(); break; case Qt::Key_M: if (player->audio()) { player->audio()->setMute(!player->audio()->isMute()); } break; case Qt::Key_R: { VideoRenderer* renderer = player->renderer(); VideoRenderer::OutAspectRatioMode r = renderer->outAspectRatioMode(); renderer->setOutAspectRatioMode(VideoRenderer::OutAspectRatioMode(((int)r+1)%2)); } break; case Qt::Key_T: { QWidget *w = qApp->activeWindow(); if (!w) return false; Qt::WindowFlags wf = w->windowFlags(); if (wf & Qt::WindowStaysOnTopHint) { qDebug("Window not stays on top"); w->setWindowFlags(wf & ~Qt::WindowStaysOnTopHint); } else { qDebug("Window stays on top"); w->setWindowFlags(wf | Qt::WindowStaysOnTopHint); } //call setParent() when changing the flags, causing the widget to be hidden w->show(); } break; case Qt::Key_F1: help(); break; default: return false; } break; } case QEvent::DragEnter: case QEvent::DragMove: { QDropEvent *e = static_cast<QDropEvent*>(event); e->acceptProposedAction(); } break; case QEvent::Drop: { QDropEvent *e = static_cast<QDropEvent*>(event); QString path = e->mimeData()->urls().first().toLocalFile(); player->stop(); player->load(path); player->play(); e->acceptProposedAction(); } break; case QEvent::GraphicsSceneContextMenu: { QGraphicsSceneContextMenuEvent *e = static_cast<QGraphicsSceneContextMenuEvent*>(event); showMenu(e->screenPos()); } break; case QEvent::ContextMenu: { QContextMenuEvent *e = static_cast<QContextMenuEvent*>(event); showMenu(e->globalPos()); } break; default: return false; } return true; //false: for text input }
//TODO: if output is null or dummy, the use duration to wait void AudioThread::run() { DPTR_D(AudioThread); //No decoder or output. No audio output is ok, just display picture if (!d.dec || !d.dec->isAvailable()) return; resetState(); Q_ASSERT(d.clock != 0); AudioDecoder *dec = static_cast<AudioDecoder*>(d.dec); AudioOutput *ao = static_cast<AudioOutput*>(d.writer); int sample_rate = dec->codecContext()->sample_rate; int channels = dec->codecContext()->channels; int csf = channels * sample_rate * sizeof(float); static const double max_len = 0.02; d.last_pts = 0; while (!d.stop) { //TODO: why put it at the end of loop then playNextFrame() not work? if (tryPause()) { //DO NOT continue, or playNextFrame() will fail if (d.stop) break; //the queue is empty and may block. should setBlocking(false) wake up cond empty? } QMutexLocker locker(&d.mutex); Q_UNUSED(locker); if (d.packets.isEmpty() && !d.stop) { d.stop = d.demux_end; if (d.stop) { break; } } Packet pkt = d.packets.take(); //wait to dequeue if (!pkt.isValid()) { qDebug("Invalid packet! flush audio codec context!!!!!!!!"); dec->flush(); continue; } d.clock->updateValue(pkt.pts); //DO NOT decode and convert if ao is not available or mute! if (dec->decode(pkt.data)) { QByteArray decoded(dec->data()); int decodedSize = decoded.size(); int decodedPos = 0; qreal delay =0; while (decodedSize > 0) { int chunk = qMin(decodedSize, int(max_len*csf)); d.clock->updateDelay(delay += (qreal)chunk/(qreal)csf); QByteArray decodedChunk(chunk, 0); //volume == 0 || mute if (ao && ao->isAvailable()) { if (!ao->isMute()) { decodedChunk = QByteArray::fromRawData(decoded.constData() + decodedPos, chunk); qreal vol = ao->volume(); if (vol != 1.0) { int len = decodedChunk.size()/sizeof(float); //TODO: why??? float *data = (float*)decodedChunk.data(); for (int i = 0; i < len; ++i) data[i] *= vol; } } ao->writeData(decodedChunk); } else { /* * why need this even if we add delay? and usleep sounds weird * the advantage is if no audio device, the play speed is ok too * So is portaudio blocking the thread when playing? */ static bool sWarn_no_ao = true; //FIXME: no warning when replay. warn only once if (sWarn_no_ao) { qDebug("Audio output not available! msleep(%lu)", (unsigned long)((qreal)chunk/(qreal)csf * 1000)); sWarn_no_ao = false; } //TODO: avoid acummulative error. External clock? msleep((unsigned long)((qreal)chunk/(qreal)csf * 1000.0)); } decodedPos += chunk; decodedSize -= chunk; } } else { //qWarning("Decode audio failed"); qreal dt = pkt.pts - d.last_pts; if (abs(dt) > 0.618 || dt < 0) { dt = 0; } //qDebug("sleep %f", dt); //TODO: avoid acummulative error. External clock? msleep((unsigned long)(dt*1000.0)); } d.last_pts = d.clock->value(); //not pkt.pts! the delay is updated! } qDebug("Audio thread stops running..."); }