mirror of
https://github.com/isledecomp/SIEdit.git
synced 2025-04-12 04:44:26 -04:00
app: abstracted media player code into its own class
This commit is contained in:
parent
3228bc8096
commit
8858b6ddf6
2 changed files with 468 additions and 378 deletions
app/viewer
|
@ -9,16 +9,6 @@
|
|||
|
||||
MediaPanel::MediaPanel(QWidget *parent) :
|
||||
Panel(parent),
|
||||
m_FmtCtx(nullptr),
|
||||
m_Packet(nullptr),
|
||||
m_VideoCodecCtx(nullptr),
|
||||
m_VideoStream(nullptr),
|
||||
m_SwsFrame(nullptr),
|
||||
m_AudioCodecCtx(nullptr),
|
||||
m_AudioStream(nullptr),
|
||||
m_SwsCtx(nullptr),
|
||||
m_SwrCtx(nullptr),
|
||||
m_IoCtx(nullptr),
|
||||
m_AudioOutput(nullptr),
|
||||
m_SliderPressed(false),
|
||||
m_vflip(false)
|
||||
|
@ -58,7 +48,10 @@ MediaPanel::MediaPanel(QWidget *parent) :
|
|||
m_PlaybackTimer->setInterval(10);
|
||||
connect(m_PlaybackTimer, &QTimer::timeout, this, &MediaPanel::TimerUpdate);
|
||||
|
||||
m_AudioNotifyDevice = new MediaAudioDevice(this);
|
||||
m_mediaInstance = new MediaInstance(this);
|
||||
connect(m_mediaInstance, &MediaInstance::EndOfFile, this, &MediaPanel::EndOfFile);
|
||||
|
||||
m_AudioNotifyDevice = new MediaAudioDevice(m_mediaInstance, this);
|
||||
}
|
||||
|
||||
MediaPanel::~MediaPanel()
|
||||
|
@ -66,14 +59,14 @@ MediaPanel::~MediaPanel()
|
|||
Close();
|
||||
}
|
||||
|
||||
qint64 MediaPanel::ReadAudio(char *data, qint64 maxlen)
|
||||
qint64 MediaInstance::ReadAudio(char *data, qint64 maxlen)
|
||||
{
|
||||
if (m_AudioFlushed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (!m_AudioFlushed && m_AudioBuffer.size() < maxlen) {
|
||||
int ret = GetNextFrame(m_AudioCodecCtx, m_AudioStream->index, m_AudioFrame);
|
||||
int ret = GetNextFrame(m_Frame);
|
||||
if (ret >= 0 || ret == AVERROR_EOF) {
|
||||
const uint8_t **in_data;
|
||||
int in_nb_samples;
|
||||
|
@ -82,13 +75,13 @@ qint64 MediaPanel::ReadAudio(char *data, qint64 maxlen)
|
|||
in_nb_samples = 0;
|
||||
m_AudioFlushed = true;
|
||||
} else {
|
||||
in_data = const_cast<const uint8_t**>(m_AudioFrame->data);
|
||||
in_nb_samples = m_AudioFrame->nb_samples;
|
||||
in_data = const_cast<const uint8_t**>(m_Frame->data);
|
||||
in_nb_samples = m_Frame->nb_samples;
|
||||
}
|
||||
|
||||
int dst_nb_samples = av_rescale_rnd(swr_get_delay(m_SwrCtx, m_AudioStream->codecpar->sample_rate) + in_nb_samples,
|
||||
m_AudioOutput->format().sampleRate(), m_AudioStream->codecpar->sample_rate, AV_ROUND_UP);
|
||||
int data_size = dst_nb_samples * av_get_bytes_per_sample(m_AudioOutputSampleFmt) * m_AudioOutput->format().channelCount();
|
||||
int dst_nb_samples = av_rescale_rnd(swr_get_delay(m_SwrCtx, m_Stream->codecpar->sample_rate) + in_nb_samples,
|
||||
m_playbackFormat.sampleRate(), m_Stream->codecpar->sample_rate, AV_ROUND_UP);
|
||||
int data_size = dst_nb_samples * av_get_bytes_per_sample(m_AudioOutputSampleFmt) * m_playbackFormat.channelCount();
|
||||
|
||||
int old_sz = m_AudioBuffer.size();
|
||||
m_AudioBuffer.resize(old_sz + data_size);
|
||||
|
@ -96,7 +89,7 @@ qint64 MediaPanel::ReadAudio(char *data, qint64 maxlen)
|
|||
uint8_t *out = reinterpret_cast<uint8_t*>(m_AudioBuffer.data() + old_sz);
|
||||
int converted = swr_convert(m_SwrCtx, &out, dst_nb_samples, in_data, in_nb_samples);
|
||||
|
||||
data_size = converted * av_get_bytes_per_sample(m_AudioOutputSampleFmt) * m_AudioOutput->format().channelCount();
|
||||
data_size = converted * av_get_bytes_per_sample(m_AudioOutputSampleFmt) * m_playbackFormat.channelCount();
|
||||
|
||||
if (m_AudioBuffer.size() != old_sz + data_size) {
|
||||
m_AudioBuffer.resize(old_sz + data_size);
|
||||
|
@ -147,10 +140,318 @@ void MediaPanel::OnOpeningData(void *data)
|
|||
{
|
||||
si::Object *o = static_cast<si::Object*>(data);
|
||||
|
||||
m_Data = o->ExtractToMemory();
|
||||
m_mediaInstance->Open(o->ExtractToMemory());
|
||||
|
||||
if (m_mediaInstance->codec_type() == AVMEDIA_TYPE_VIDEO) {
|
||||
// Heuristic to flip phoneme flics vertically
|
||||
m_vflip = (o->name().find("_Pho_") != std::string::npos);
|
||||
|
||||
VideoUpdate(0);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::OnClosingData(void *data)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void MediaPanel::Close()
|
||||
{
|
||||
Play(false);
|
||||
|
||||
m_mediaInstance->Close();
|
||||
|
||||
m_PlayheadSlider->setValue(0);
|
||||
|
||||
m_ImgViewer->setPixmap(QPixmap());
|
||||
|
||||
m_vflip = false;
|
||||
}
|
||||
|
||||
QImage MediaInstance::GetVideoFrame(float t)
|
||||
{
|
||||
// Convert percent to duration seconds
|
||||
int64_t ts = PercentToTimestamp(t);
|
||||
//int64_t second = std::ceil(flipped);
|
||||
|
||||
AVFrame *using_frame = nullptr;
|
||||
for (auto it=m_FrameQueue.begin(); it!=m_FrameQueue.end(); it++) {
|
||||
auto next = it;
|
||||
next++;
|
||||
|
||||
if ((*it)->pts == ts
|
||||
|| (next != m_FrameQueue.end() && (*next)->pts > ts)) {
|
||||
using_frame = *it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!using_frame) {
|
||||
// Determine if the queue will eventually get this frame
|
||||
if (m_FrameQueue.empty()
|
||||
//|| ts > m_FrameQueue.back()->pts + second
|
||||
|| ts < m_FrameQueue.front()->pts) {
|
||||
ClearQueue();
|
||||
av_seek_frame(m_FmtCtx, m_Stream->index, ts, AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
|
||||
while (m_FrameQueue.empty() || m_FrameQueue.back()->pts < ts) {
|
||||
AVFrame *f = av_frame_alloc();
|
||||
int ret = GetNextFrame(f);
|
||||
if (ret < 0) {
|
||||
av_frame_free(&f);
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
emit EndOfFile();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
AVFrame *previous = nullptr;
|
||||
if (!m_FrameQueue.empty()) {
|
||||
previous = m_FrameQueue.back();
|
||||
}
|
||||
|
||||
m_FrameQueue.push_back(f);
|
||||
|
||||
if (previous && f->pts > ts) {
|
||||
using_frame = previous;
|
||||
break;
|
||||
} else if (f->pts == ts) {
|
||||
using_frame = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (using_frame) {
|
||||
if (using_frame->pts != m_Frame->pts) {
|
||||
m_Frame->pts = using_frame->pts;
|
||||
|
||||
sws_scale(m_SwsCtx, using_frame->data, using_frame->linesize, 0, using_frame->height,
|
||||
m_Frame->data, m_Frame->linesize);
|
||||
|
||||
return QImage(m_Frame->data[0], m_Frame->width, m_Frame->height, m_Frame->linesize[0], QImage::Format_RGBA8888);
|
||||
}
|
||||
}
|
||||
|
||||
return QImage();
|
||||
}
|
||||
|
||||
int MediaInstance::GetNextFrame(AVFrame *frame)
|
||||
{
|
||||
int ret;
|
||||
av_frame_unref(frame);
|
||||
while ((ret = avcodec_receive_frame(m_CodecCtx, frame)) == AVERROR(EAGAIN)) {
|
||||
av_packet_unref(m_Packet);
|
||||
ret = av_read_frame(m_FmtCtx, m_Packet);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (m_Packet->stream_index == m_Stream->index) {
|
||||
ret = avcodec_send_packet(m_CodecCtx, m_Packet);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MediaPanel::StartAudioPlayback()
|
||||
{
|
||||
auto output_dev = QAudioDeviceInfo::defaultOutputDevice();
|
||||
auto fmt = output_dev.preferredFormat();
|
||||
|
||||
if (m_mediaInstance->StartPlayingAudio(output_dev, fmt)) {
|
||||
m_AudioOutput = new QAudioOutput(output_dev, fmt, this);
|
||||
m_AudioNotifyDevice->open(QIODevice::ReadOnly);
|
||||
connect(m_AudioOutput, &QAudioOutput::stateChanged, this, &MediaPanel::AudioStateChanged);
|
||||
m_AudioOutput->start(m_AudioNotifyDevice);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::VideoUpdate(float t)
|
||||
{
|
||||
QImage img = m_mediaInstance->GetVideoFrame(t);
|
||||
if (!img.isNull()) {
|
||||
if (m_vflip) {
|
||||
img = img.mirrored(false, true);
|
||||
}
|
||||
|
||||
m_ImgViewer->setPixmap(QPixmap::fromImage(img));
|
||||
}
|
||||
}
|
||||
|
||||
float MediaPanel::GetRealSliderValue() const
|
||||
{
|
||||
return float(m_PlayheadSlider->value()) / m_PlayheadSlider->maximum();
|
||||
}
|
||||
|
||||
int MediaPanel::GetFakeSliderValueFromReal(float t) const
|
||||
{
|
||||
return t * m_PlayheadSlider->maximum();
|
||||
}
|
||||
|
||||
void MediaPanel::Play(bool e)
|
||||
{
|
||||
if (m_AudioOutput) {
|
||||
m_AudioOutput->stop();
|
||||
delete m_AudioOutput;
|
||||
m_AudioOutput = nullptr;
|
||||
|
||||
m_AudioNotifyDevice->close();
|
||||
}
|
||||
|
||||
if (e) {
|
||||
if (m_mediaInstance->codec_type() == AVMEDIA_TYPE_VIDEO) {
|
||||
m_PlaybackOffset = GetRealSliderValue();
|
||||
} else {
|
||||
m_PlaybackOffset = 0;
|
||||
if (m_mediaInstance->codec_type() == AVMEDIA_TYPE_AUDIO) {
|
||||
StartAudioPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
m_PlaybackStart = QDateTime::currentMSecsSinceEpoch();
|
||||
m_PlaybackTimer->start();
|
||||
} else {
|
||||
m_PlaybackTimer->stop();
|
||||
}
|
||||
m_PlayBtn->setChecked(e);
|
||||
}
|
||||
|
||||
void MediaPanel::TimerUpdate()
|
||||
{
|
||||
if (!m_SliderPressed && m_mediaInstance->GetStreamPosition() != AV_NOPTS_VALUE) {
|
||||
m_PlayheadSlider->setValue(GetFakeSliderValueFromReal(m_mediaInstance->GetTime()));
|
||||
}
|
||||
|
||||
if (m_mediaInstance->codec_type() == AVMEDIA_TYPE_VIDEO) {
|
||||
float now_seconds = float(QDateTime::currentMSecsSinceEpoch() - m_PlaybackStart) * 0.001f;
|
||||
float now = m_mediaInstance->SecondsToPercent(now_seconds);
|
||||
|
||||
VideoUpdate(now + m_PlaybackOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::UpdateVideo()
|
||||
{
|
||||
SliderMoved(m_PlayheadSlider->value());
|
||||
}
|
||||
|
||||
void MediaPanel::SliderPressed()
|
||||
{
|
||||
m_SliderPressed = true;
|
||||
}
|
||||
|
||||
void MediaPanel::SliderMoved(int i)
|
||||
{
|
||||
if (m_mediaInstance->codec_type() == AVMEDIA_TYPE_VIDEO) {
|
||||
float f = GetRealSliderValue();
|
||||
m_PlaybackOffset = f;
|
||||
m_PlaybackStart = QDateTime::currentMSecsSinceEpoch();
|
||||
VideoUpdate(f);
|
||||
} else if (m_mediaInstance->codec_type() == AVMEDIA_TYPE_AUDIO) {
|
||||
m_mediaInstance->Seek(GetRealSliderValue());
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::SliderReleased()
|
||||
{
|
||||
m_SliderPressed = false;
|
||||
}
|
||||
|
||||
void MediaPanel::AudioStateChanged(QAudio::State state)
|
||||
{
|
||||
if (state == QAudio::IdleState) {
|
||||
Play(false);
|
||||
m_PlayheadSlider->setValue(m_PlayheadSlider->maximum());
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::LabelContextMenuTriggered(const QPoint &pos)
|
||||
{
|
||||
QMenu m(this);
|
||||
|
||||
auto vert_flip = m.addAction(tr("Flip Vertically"));
|
||||
vert_flip->setCheckable(true);
|
||||
vert_flip->setChecked(m_vflip);
|
||||
connect(vert_flip, &QAction::triggered, this, [this](bool e){
|
||||
m_vflip = e;
|
||||
UpdateVideo();
|
||||
});
|
||||
|
||||
m.exec(static_cast<QWidget*>(sender())->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void MediaPanel::EndOfFile()
|
||||
{
|
||||
if (IsPlaying()) {
|
||||
Play(false);
|
||||
if (!m_SliderPressed) {
|
||||
m_PlayheadSlider->setValue(m_PlayheadSlider->maximum());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaAudioDevice::MediaAudioDevice(MediaInstance *o, QObject *parent) :
|
||||
QIODevice(parent)
|
||||
{
|
||||
m_mediaInstance = o;
|
||||
}
|
||||
|
||||
qint64 MediaAudioDevice::readData(char *data, qint64 maxSize)
|
||||
{
|
||||
return m_mediaInstance->ReadAudio(data, maxSize);
|
||||
}
|
||||
|
||||
qint64 MediaAudioDevice::writeData(const char *data, qint64 maxSize)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent) :
|
||||
QSlider(orientation, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ClickableSlider::ClickableSlider(QWidget *parent) :
|
||||
QSlider(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ClickableSlider::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
int v = double(e->pos().x()) / double(width()) * this->maximum();
|
||||
setValue(v);
|
||||
emit sliderMoved(v);
|
||||
|
||||
QSlider::mousePressEvent(e);
|
||||
}
|
||||
|
||||
MediaInstance::MediaInstance(QObject *parent) :
|
||||
m_FmtCtx(nullptr),
|
||||
m_Packet(nullptr),
|
||||
m_CodecCtx(nullptr),
|
||||
m_Stream(nullptr),
|
||||
m_Frame(nullptr),
|
||||
m_SwsCtx(nullptr),
|
||||
m_SwrCtx(nullptr),
|
||||
m_IoCtx(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MediaInstance::Open(const si::MemoryBuffer &buf)
|
||||
{
|
||||
static const size_t buf_sz = 4096;
|
||||
|
||||
m_Data = buf;
|
||||
|
||||
m_IoCtx = avio_alloc_context(
|
||||
(unsigned char *) av_malloc(buf_sz),
|
||||
buf_sz,
|
||||
|
@ -177,75 +478,52 @@ void MediaPanel::OnOpeningData(void *data)
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_FmtCtx->nb_streams == 0) {
|
||||
qWarning() << "No streams in file";
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_Stream = m_FmtCtx->streams[0];
|
||||
|
||||
const AVCodec *decoder = avcodec_find_decoder(m_Stream->codecpar->codec_id);
|
||||
if (!decoder) {
|
||||
qWarning() << "Failed to find decoder for type" << avcodec_get_name(m_Stream->codecpar->codec_id);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_CodecCtx = avcodec_alloc_context3(decoder);
|
||||
avcodec_parameters_to_context(m_CodecCtx, m_Stream->codecpar);
|
||||
avcodec_open2(m_CodecCtx, decoder, nullptr);
|
||||
|
||||
m_Packet = av_packet_alloc();
|
||||
m_SwsFrame = av_frame_alloc();
|
||||
m_AudioFrame = av_frame_alloc();
|
||||
m_Frame = av_frame_alloc();
|
||||
|
||||
for (unsigned int i=0; i<m_FmtCtx->nb_streams; i++) {
|
||||
AVStream *s = m_FmtCtx->streams[i];
|
||||
if (m_Stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
const AVPixelFormat dest = AV_PIX_FMT_RGBA;
|
||||
m_SwsCtx = sws_getContext(m_Stream->codecpar->width,
|
||||
m_Stream->codecpar->height,
|
||||
static_cast<AVPixelFormat>(m_Stream->codecpar->format),
|
||||
m_Stream->codecpar->width,
|
||||
m_Stream->codecpar->height,
|
||||
dest,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
const AVCodec *decoder = avcodec_find_decoder(s->codecpar->codec_id);
|
||||
|
||||
if (decoder) {
|
||||
if (!m_VideoCodecCtx && s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
m_VideoStream = s;
|
||||
|
||||
m_VideoCodecCtx = avcodec_alloc_context3(decoder);
|
||||
avcodec_parameters_to_context(m_VideoCodecCtx, s->codecpar);
|
||||
avcodec_open2(m_VideoCodecCtx, decoder, nullptr);
|
||||
|
||||
const AVPixelFormat dest = AV_PIX_FMT_RGBA;
|
||||
m_SwsCtx = sws_getContext(s->codecpar->width,
|
||||
s->codecpar->height,
|
||||
static_cast<AVPixelFormat>(s->codecpar->format),
|
||||
s->codecpar->width,
|
||||
s->codecpar->height,
|
||||
dest,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
m_SwsFrame = av_frame_alloc();
|
||||
m_SwsFrame->width = s->codecpar->width;
|
||||
m_SwsFrame->height = s->codecpar->height;
|
||||
m_SwsFrame->format = dest;
|
||||
av_frame_get_buffer(m_SwsFrame, 0);
|
||||
}
|
||||
|
||||
if (!m_AudioCodecCtx && s->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
m_AudioStream = s;
|
||||
|
||||
m_AudioCodecCtx = avcodec_alloc_context3(decoder);
|
||||
avcodec_parameters_to_context(m_AudioCodecCtx, s->codecpar);
|
||||
avcodec_open2(m_AudioCodecCtx, decoder, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_VideoCodecCtx) {
|
||||
// Heuristic to flip phoneme flics vertically
|
||||
m_vflip = (o->name().find("_Pho_") != std::string::npos);
|
||||
|
||||
VideoUpdate(0);
|
||||
m_Frame->width = m_Stream->codecpar->width;
|
||||
m_Frame->height = m_Stream->codecpar->height;
|
||||
m_Frame->format = dest;
|
||||
av_frame_get_buffer(m_Frame, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::OnClosingData(void *data)
|
||||
void MediaInstance::Close()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void MediaPanel::Close()
|
||||
{
|
||||
Play(false);
|
||||
|
||||
if (m_VideoCodecCtx) {
|
||||
avcodec_free_context(&m_VideoCodecCtx);
|
||||
}
|
||||
|
||||
if (m_AudioCodecCtx) {
|
||||
avcodec_free_context(&m_AudioCodecCtx);
|
||||
if (m_CodecCtx) {
|
||||
avcodec_free_context(&m_CodecCtx);
|
||||
}
|
||||
|
||||
if (m_SwsCtx) {
|
||||
|
@ -261,12 +539,8 @@ void MediaPanel::Close()
|
|||
av_packet_free(&m_Packet);
|
||||
}
|
||||
|
||||
if (m_SwsFrame) {
|
||||
av_frame_free(&m_SwsFrame);
|
||||
}
|
||||
|
||||
if (m_AudioFrame) {
|
||||
av_frame_free(&m_AudioFrame);
|
||||
if (m_Frame) {
|
||||
av_frame_free(&m_Frame);
|
||||
}
|
||||
|
||||
ClearQueue();
|
||||
|
@ -281,137 +555,13 @@ void MediaPanel::Close()
|
|||
m_IoCtx = nullptr;
|
||||
}
|
||||
|
||||
m_VideoStream = nullptr;
|
||||
m_AudioStream = nullptr;
|
||||
m_Stream = nullptr;
|
||||
|
||||
m_Data.Close();
|
||||
|
||||
m_PlayheadSlider->setValue(0);
|
||||
|
||||
m_ImgViewer->setPixmap(QPixmap());
|
||||
|
||||
m_vflip = false;
|
||||
}
|
||||
|
||||
void MediaPanel::VideoUpdate(float t)
|
||||
bool MediaInstance::StartPlayingAudio(const QAudioDeviceInfo &output_dev, const QAudioFormat &fmt)
|
||||
{
|
||||
double flipped = av_q2d(av_inv_q(m_VideoStream->time_base));
|
||||
int64_t ts = std::floor(t * flipped);
|
||||
//int64_t second = std::ceil(flipped);
|
||||
|
||||
AVFrame *using_frame = nullptr;
|
||||
for (auto it=m_FrameQueue.begin(); it!=m_FrameQueue.end(); it++) {
|
||||
auto next = it;
|
||||
next++;
|
||||
|
||||
if ((*it)->pts == ts
|
||||
|| (next != m_FrameQueue.end() && (*next)->pts > ts)) {
|
||||
using_frame = *it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!using_frame) {
|
||||
// Determine if the queue will eventually get this frame
|
||||
if (m_FrameQueue.empty()
|
||||
//|| ts > m_FrameQueue.back()->pts + second
|
||||
|| ts < m_FrameQueue.front()->pts) {
|
||||
ClearQueue();
|
||||
av_seek_frame(m_FmtCtx, m_VideoStream->index, ts, AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
|
||||
while (m_FrameQueue.empty() || m_FrameQueue.back()->pts < ts) {
|
||||
AVFrame *f = av_frame_alloc();
|
||||
int ret = GetNextFrame(m_VideoCodecCtx, m_VideoStream->index, f);
|
||||
if (ret < 0) {
|
||||
av_frame_free(&f);
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
Play(false);
|
||||
m_PlayheadSlider->setValue(m_PlayheadSlider->maximum());
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
AVFrame *previous = nullptr;
|
||||
if (!m_FrameQueue.empty()) {
|
||||
previous = m_FrameQueue.back();
|
||||
}
|
||||
|
||||
m_FrameQueue.push_back(f);
|
||||
|
||||
if (previous && f->pts > ts) {
|
||||
using_frame = previous;
|
||||
break;
|
||||
} else if (f->pts == ts) {
|
||||
using_frame = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (using_frame) {
|
||||
if (using_frame->pts != m_SwsFrame->pts) {
|
||||
m_SwsFrame->pts = using_frame->pts;
|
||||
|
||||
sws_scale(m_SwsCtx, using_frame->data, using_frame->linesize, 0, using_frame->height,
|
||||
m_SwsFrame->data, m_SwsFrame->linesize);
|
||||
|
||||
QImage img(m_SwsFrame->data[0], m_SwsFrame->width, m_SwsFrame->height, m_SwsFrame->linesize[0], QImage::Format_RGBA8888);
|
||||
|
||||
if (m_vflip) {
|
||||
img = img.mirrored(false, true);
|
||||
}
|
||||
|
||||
m_ImgViewer->setPixmap(QPixmap::fromImage(img));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::AudioSeek(float t)
|
||||
{
|
||||
double flipped = av_q2d(av_inv_q(m_AudioStream->time_base));
|
||||
int64_t ts = std::floor(t * flipped);
|
||||
|
||||
av_seek_frame(m_FmtCtx, m_AudioStream->index, ts, AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
|
||||
int MediaPanel::GetNextFrame(AVCodecContext *cctx, unsigned int stream, AVFrame *frame)
|
||||
{
|
||||
int ret;
|
||||
av_frame_unref(frame);
|
||||
while ((ret = avcodec_receive_frame(cctx, frame)) == AVERROR(EAGAIN)) {
|
||||
av_packet_unref(m_Packet);
|
||||
ret = av_read_frame(m_FmtCtx, m_Packet);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (m_Packet->stream_index == stream) {
|
||||
ret = avcodec_send_packet(cctx, m_Packet);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MediaPanel::ClearQueue()
|
||||
{
|
||||
while (!m_FrameQueue.empty()) {
|
||||
av_frame_free(&m_FrameQueue.front());
|
||||
m_FrameQueue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::StartAudioPlayback()
|
||||
{
|
||||
auto output_dev = QAudioDeviceInfo::defaultOutputDevice();
|
||||
auto fmt = output_dev.preferredFormat();
|
||||
|
||||
AVSampleFormat smp_fmt = AV_SAMPLE_FMT_S16;
|
||||
switch (fmt.sampleType()) {
|
||||
case QAudioFormat::Unknown:
|
||||
|
@ -447,15 +597,17 @@ void MediaPanel::StartAudioPlayback()
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_playbackFormat = fmt;
|
||||
m_AudioOutputSampleFmt = smp_fmt;
|
||||
|
||||
m_SwrCtx = swr_alloc_set_opts(nullptr,
|
||||
av_get_default_channel_layout(fmt.channelCount()),
|
||||
smp_fmt,
|
||||
fmt.sampleRate(),
|
||||
av_get_default_channel_layout(m_AudioStream->codecpar->channels),
|
||||
static_cast<AVSampleFormat>(m_AudioStream->codecpar->format),
|
||||
m_AudioStream->codecpar->sample_rate,
|
||||
av_get_default_channel_layout(m_Stream->codecpar->channels),
|
||||
static_cast<AVSampleFormat>(m_Stream->codecpar->format),
|
||||
m_Stream->codecpar->sample_rate,
|
||||
0, nullptr);
|
||||
if (!m_SwrCtx) {
|
||||
qCritical() << "Failed to alloc swr ctx";
|
||||
|
@ -464,155 +616,42 @@ void MediaPanel::StartAudioPlayback()
|
|||
qCritical() << "Failed to init swr ctx";
|
||||
} else {
|
||||
if (m_AudioFlushed) {
|
||||
AudioSeek(0);
|
||||
Seek(0);
|
||||
m_AudioFlushed = false;
|
||||
}
|
||||
m_AudioBuffer.clear();
|
||||
|
||||
m_AudioOutput = new QAudioOutput(output_dev, fmt, this);
|
||||
m_AudioNotifyDevice->open(QIODevice::ReadOnly);
|
||||
connect(m_AudioOutput, &QAudioOutput::stateChanged, this, &MediaPanel::AudioStateChanged);
|
||||
m_AudioOutput->start(m_AudioNotifyDevice);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float MediaPanel::SliderValueToFloatSeconds(int i, int max, AVStream *s)
|
||||
void MediaInstance::Seek(float t)
|
||||
{
|
||||
float percent = float(i) / float(max);
|
||||
float duration = float(s->time_base.num) * float(s->duration) / float(s->time_base.den);
|
||||
return percent * duration;
|
||||
av_seek_frame(m_FmtCtx, m_Stream->index, PercentToTimestamp(t), AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
|
||||
void MediaPanel::Play(bool e)
|
||||
void MediaInstance::ClearQueue()
|
||||
{
|
||||
if (m_AudioOutput) {
|
||||
m_AudioOutput->stop();
|
||||
delete m_AudioOutput;
|
||||
m_AudioOutput = nullptr;
|
||||
|
||||
m_AudioNotifyDevice->close();
|
||||
}
|
||||
|
||||
if (e) {
|
||||
if (m_VideoStream) {
|
||||
m_PlaybackOffset = SliderValueToFloatSeconds(m_PlayheadSlider->value(), m_PlayheadSlider->maximum(), m_VideoStream);
|
||||
} else {
|
||||
m_PlaybackOffset = 0;
|
||||
}
|
||||
|
||||
if (m_AudioStream) {
|
||||
StartAudioPlayback();
|
||||
}
|
||||
|
||||
m_PlaybackStart = QDateTime::currentMSecsSinceEpoch();
|
||||
m_PlaybackTimer->start();
|
||||
} else {
|
||||
m_PlaybackTimer->stop();
|
||||
}
|
||||
m_PlayBtn->setChecked(e);
|
||||
}
|
||||
|
||||
void MediaPanel::TimerUpdate()
|
||||
{
|
||||
float now = float(QDateTime::currentMSecsSinceEpoch() - m_PlaybackStart) * 0.001f;
|
||||
if (m_VideoStream) {
|
||||
VideoUpdate(now + m_PlaybackOffset);
|
||||
|
||||
if (!m_SliderPressed && m_SwsFrame->pts != AV_NOPTS_VALUE) {
|
||||
float percent = float(m_SwsFrame->pts) / float(m_VideoStream->duration);
|
||||
m_PlayheadSlider->setValue(percent * m_PlayheadSlider->maximum());
|
||||
}
|
||||
} else if (m_AudioStream) {
|
||||
if (!m_SliderPressed && m_AudioFrame->pts != AV_NOPTS_VALUE) {
|
||||
float percent = float(m_AudioFrame->pts) / float(m_AudioStream->duration);
|
||||
m_PlayheadSlider->setValue(percent * m_PlayheadSlider->maximum());
|
||||
}
|
||||
while (!m_FrameQueue.empty()) {
|
||||
av_frame_free(&m_FrameQueue.front());
|
||||
m_FrameQueue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::UpdateVideo()
|
||||
float MediaInstance::PercentToSeconds(float t) const
|
||||
{
|
||||
SliderMoved(m_PlayheadSlider->value());
|
||||
return t * (float(m_Stream->time_base.num) * float(m_Stream->duration) / float(m_Stream->time_base.den));
|
||||
}
|
||||
|
||||
void MediaPanel::SliderPressed()
|
||||
float MediaInstance::SecondsToPercent(float t) const
|
||||
{
|
||||
m_SliderPressed = true;
|
||||
return t / (float(m_Stream->time_base.num) * float(m_Stream->duration) / float(m_Stream->time_base.den));
|
||||
}
|
||||
|
||||
void MediaPanel::SliderMoved(int i)
|
||||
int64_t MediaInstance::PercentToTimestamp(float t) const
|
||||
{
|
||||
if (m_VideoStream) {
|
||||
float f = SliderValueToFloatSeconds(i, m_PlayheadSlider->maximum(), m_VideoStream);
|
||||
m_PlaybackOffset = f;
|
||||
m_PlaybackStart = QDateTime::currentMSecsSinceEpoch();
|
||||
VideoUpdate(f);
|
||||
}
|
||||
if (m_AudioStream) {
|
||||
AudioSeek(SliderValueToFloatSeconds(i, m_PlayheadSlider->maximum(), m_AudioStream));
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::SliderReleased()
|
||||
{
|
||||
m_SliderPressed = false;
|
||||
}
|
||||
|
||||
void MediaPanel::AudioStateChanged(QAudio::State state)
|
||||
{
|
||||
if (state == QAudio::IdleState) {
|
||||
Play(false);
|
||||
m_PlayheadSlider->setValue(m_PlayheadSlider->maximum());
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPanel::LabelContextMenuTriggered(const QPoint &pos)
|
||||
{
|
||||
QMenu m(this);
|
||||
|
||||
auto vert_flip = m.addAction(tr("Flip Vertically"));
|
||||
vert_flip->setCheckable(true);
|
||||
vert_flip->setChecked(m_vflip);
|
||||
connect(vert_flip, &QAction::triggered, this, [this](bool e){
|
||||
m_vflip = e;
|
||||
UpdateVideo();
|
||||
});
|
||||
|
||||
m.exec(static_cast<QWidget*>(sender())->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
MediaAudioDevice::MediaAudioDevice(MediaPanel *o) :
|
||||
QIODevice(o)
|
||||
{
|
||||
m_MediaPanel = o;
|
||||
}
|
||||
|
||||
qint64 MediaAudioDevice::readData(char *data, qint64 maxSize)
|
||||
{
|
||||
return m_MediaPanel->ReadAudio(data, maxSize);
|
||||
}
|
||||
|
||||
qint64 MediaAudioDevice::writeData(const char *data, qint64 maxSize)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent) :
|
||||
QSlider(orientation, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ClickableSlider::ClickableSlider(QWidget *parent) :
|
||||
QSlider(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ClickableSlider::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
int v = double(e->pos().x()) / double(width()) * this->maximum();
|
||||
setValue(v);
|
||||
emit sliderMoved(v);
|
||||
|
||||
QSlider::mousePressEvent(e);
|
||||
return std::floor(t * float(m_Stream->duration));
|
||||
}
|
||||
|
|
|
@ -18,6 +18,76 @@ extern "C" {
|
|||
|
||||
#include "panel.h"
|
||||
|
||||
class MediaInstance : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MediaInstance(QObject *parent = nullptr);
|
||||
|
||||
void Open(const si::MemoryBuffer &buf);
|
||||
|
||||
void Close();
|
||||
|
||||
AVMediaType codec_type() const
|
||||
{
|
||||
return m_Stream ? m_Stream->codecpar->codec_type : AVMEDIA_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
bool StartPlayingAudio(const QAudioDeviceInfo &output_dev, const QAudioFormat &fmt);
|
||||
|
||||
void Seek(float t);
|
||||
|
||||
int GetNextFrame(AVFrame *frame);
|
||||
|
||||
qint64 ReadAudio(char *data, qint64 maxlen);
|
||||
|
||||
QImage GetVideoFrame(float f);
|
||||
|
||||
int64_t GetStreamPosition() const
|
||||
{
|
||||
return m_Frame->pts;
|
||||
}
|
||||
|
||||
float GetTime() const
|
||||
{
|
||||
return float(m_Frame->pts) / m_Stream->duration;
|
||||
}
|
||||
|
||||
float PercentToSeconds(float t) const;
|
||||
float SecondsToPercent(float t) const;
|
||||
|
||||
int64_t PercentToTimestamp(float t) const;
|
||||
|
||||
signals:
|
||||
void EndOfFile();
|
||||
|
||||
private:
|
||||
void ClearQueue();
|
||||
|
||||
AVFormatContext *m_FmtCtx;
|
||||
AVStream *m_Stream;
|
||||
std::list<AVFrame*> m_FrameQueue;
|
||||
|
||||
AVPacket *m_Packet;
|
||||
AVFrame *m_Frame;
|
||||
|
||||
AVCodecContext *m_CodecCtx;
|
||||
|
||||
SwsContext *m_SwsCtx;
|
||||
SwrContext *m_SwrCtx;
|
||||
|
||||
bool m_AudioFlushed;
|
||||
QByteArray m_AudioBuffer;
|
||||
|
||||
AVIOContext *m_IoCtx;
|
||||
|
||||
si::MemoryBuffer m_Data;
|
||||
|
||||
QAudioFormat m_playbackFormat;
|
||||
AVSampleFormat m_AudioOutputSampleFmt;
|
||||
|
||||
};
|
||||
|
||||
class MediaPanel : public Panel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -25,7 +95,10 @@ public:
|
|||
MediaPanel(QWidget *parent = nullptr);
|
||||
virtual ~MediaPanel() override;
|
||||
|
||||
qint64 ReadAudio(char *data, qint64 maxlen);
|
||||
bool IsPlaying() const
|
||||
{
|
||||
return m_PlaybackTimer->isActive();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void OnOpeningData(void *data) override;
|
||||
|
@ -34,48 +107,24 @@ protected:
|
|||
private:
|
||||
void Close();
|
||||
|
||||
void VideoUpdate(float t);
|
||||
void AudioSeek(float t);
|
||||
|
||||
int GetNextFrame(AVCodecContext *cctx, unsigned int stream, AVFrame *frame);
|
||||
|
||||
void ClearQueue();
|
||||
|
||||
void StartAudioPlayback();
|
||||
|
||||
static float SliderValueToFloatSeconds(int i, int max, AVStream *s);
|
||||
void VideoUpdate(float t);
|
||||
|
||||
AVFormatContext *m_FmtCtx;
|
||||
AVPacket *m_Packet;
|
||||
std::list<AVFrame*> m_FrameQueue;
|
||||
|
||||
AVCodecContext *m_VideoCodecCtx;
|
||||
AVStream *m_VideoStream;
|
||||
AVFrame *m_SwsFrame;
|
||||
AVFrame *m_AudioFrame;
|
||||
|
||||
AVCodecContext *m_AudioCodecCtx;
|
||||
AVStream *m_AudioStream;
|
||||
|
||||
SwsContext *m_SwsCtx;
|
||||
SwrContext *m_SwrCtx;
|
||||
|
||||
AVIOContext *m_IoCtx;
|
||||
|
||||
si::MemoryBuffer m_Data;
|
||||
float GetRealSliderValue() const;
|
||||
int GetFakeSliderValueFromReal(float t) const;
|
||||
|
||||
QLabel *m_ImgViewer;
|
||||
|
||||
MediaInstance *m_mediaInstance;
|
||||
|
||||
QAudioOutput *m_AudioOutput;
|
||||
QIODevice *m_AudioNotifyDevice;
|
||||
QByteArray m_AudioBuffer;
|
||||
AVSampleFormat m_AudioOutputSampleFmt;
|
||||
QSlider *m_PlayheadSlider;
|
||||
QPushButton *m_PlayBtn;
|
||||
QTimer *m_PlaybackTimer;
|
||||
qint64 m_PlaybackStart;
|
||||
float m_PlaybackOffset;
|
||||
bool m_AudioFlushed;
|
||||
bool m_SliderPressed;
|
||||
bool m_vflip;
|
||||
|
||||
|
@ -94,20 +143,22 @@ private slots:
|
|||
|
||||
void LabelContextMenuTriggered(const QPoint &pos);
|
||||
|
||||
void EndOfFile();
|
||||
|
||||
};
|
||||
|
||||
class MediaAudioDevice : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MediaAudioDevice(MediaPanel *o = nullptr);
|
||||
MediaAudioDevice(MediaInstance *o, QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
virtual qint64 readData(char *data, qint64 maxSize) override;
|
||||
virtual qint64 writeData(const char *data, qint64 maxSize) override;
|
||||
|
||||
private:
|
||||
MediaPanel *m_MediaPanel;
|
||||
MediaInstance *m_mediaInstance;
|
||||
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue