Using VLC imem to play an h264 video file from memory but receiving error "main stream error: cannot pre fill buffer"
Using VLC imem to play an h264 video file from memory but receiving error "main stream error: cannot pre fill buffer"
我有一个加载到内存中的 h264 视频文件,我尝试使用参数 "imem-cat=4" 使用 imem 播放它,这样 vlc 将使用访问模块来解复用视频,然后 vlc 启动并成功接收我的 imem 参数:
[0x7f38a0000e28] access_imem demux debug: Using get(0x404e1d), release(0x404e91), data(0x7fff5b4a9430), cookie("IMEM")
这个类别也意味着我不必提供DTS和PTS。 VLC 的 imem 模块没有很好的文档记录,但我在几个地方找到了提示,例如
https://forum.videolan.org/viewtopic.php?t=111917
https://forum.videolan.org/viewtopic.php?f=32&t=93842
Play video using libVLC from memory in python
我的 imem-get 函数只是在第一次调用时将缓冲区指针设置为视频数据,返回 0,在任何进一步调用它时 returns 1 表示没有更多数据:
int MyImemGetCallback (void *data,
const char *cookie,
int64_t *dts,
int64_t *pts,
unsigned *flags,
size_t * bufferSize,
void ** buffer)
{
ImemData* imem = (ImemData*)data;
cookie = imem->cookieString;
if(imem == NULL || imem->allBuffered==true) //indicate all data has been get()ted
return 1;
*buffer = (void*) imem->video;
bufferSize = (size_t*) &(imem->bytes);
imem->allBuffered=true;
return 0;
}
不幸的是,在第一次调用后我收到以下错误:
[0x189cb18] main input debug: Creating an input for 'imem://'
[0x189cb18] main input debug: using timeshift granularity of 50 MiB, in path '/tmp'
[0x189cb18] main input debug: `imem://' gives access `imem' demux `' path `'
[0x189cb18] main input debug: creating demux: access='imem' demux='' location='' file='(null)'
[0x7f2808000e28] main demux debug: looking for access_demux module matching "imem": 20 candidates
[0x7f2808000e28] access_imem demux debug: Using get(0x404e1d), release(0x404e91), data(0x7ffe0da3b940), cookie("h264")
[0x7f2808000e28] main demux debug: no access_demux modules matched
[0x189cb18] main input debug: creating access 'imem' location='', path='(null)'
[0x7f2808001958] main access debug: looking for access module matching "imem": 25 candidates
[0x7f2808001958] access_imem access debug: Using get(0x404e1d), release(0x404e91), data(0x7ffe0da3b940), cookie("h264")
[0x7f2808001958] main access debug: using access module "access_imem"
[0x7f2808000e28] main stream debug: Using block method for AStream*
[0x7f2808000e28] main stream debug: starting pre-buffering
[0x7f2808000e28] main stream error: cannot pre fill buffer
[0x7f2808001958] main access debug: removing module "access_imem"
[0x189cb18] main input warning: cannot create a stream_t from access
[0x17d7298] main libvlc debug: removing all interfaces
[0x17d7298] main libvlc debug: exiting
[0x17d7298] main libvlc debug: no exit handler
[0x17d7298] main libvlc debug: removing stats
出于某种原因,vlc 似乎无法访问视频数据,但错误消息不是很有用,通常指的是网络流而不是内存位置。
有没有人以这种方式成功使用 imem 或对可能出现的问题有任何想法?视频在 VLC 中完美地从磁盘播放。
感谢您的帮助。
编辑
道具界面好像不支持这样玩。但是,libVLC 提供了 libvlc_media_t 和 livblc_media_new_callbacks 这可能会让我实现我想要的。如果我成功了,我会报告。
所以我无法让 Imem 工作,但是在 VLC 论坛上我被指向了 3.0.0 版中可用的新 API。我不得不删除我当前安装的 vlc 和 libvlc-dev,并将 VLC 每日构建 PPA 添加到 Ubuntu 安装,然后安装这些版本。
API是libvlc_media_new_callbacks:
LIBVLC_API libvlc_media_t * libvlc_media_new_callbacks
(
libvlc_instance_t *instance,
libvlc_media_open_cb open_cb,
libvlc_media_read_cb read_cb,
libvlc_media_seek_cb seek_cb,
libvlc_media_close_cb close_cb,
void *opaque
);
您必须实现每个回调才能让 VLC 访问内存中的流。尽管文档指出不需要实现 seek() 回调,但没有它我无法播放 h264 视频。
open() 回调应该传递一个指向您的视频数据的指针,我推荐一个容器 class 这样您就可以存储读取的最后一个字节的索引。
read() 回调用于将 len 字节读入传递指针的缓冲区。在这里,您可以将 len 或更少的字节写入缓冲区和 return 复制的字节数,阻塞直到准备好一些字节,return 0 表示 EOF 或 -1 表示错误。
seek() 回调用于设置下一个 read() 发生的字节索引。
最后 close() 没有 return 任何东西,用于整理。
这里是一个 read() 实现的例子:
class MemVideoData
{
public:
MemVideoData(char *data, int data_bytes) : video(data), bytes(data_bytes), lastByteIndex(0) {} //init
~MemVideoData() {}
char* video; //pointer to video in memory
int bytes;
int lastByteIndex;
};
ssize_t memVideo_read(void *opaque, unsigned char* buf, size_t len)
{
//TODO: block if not end of stream but no bytes available
MemVideoData *mVid = (MemVideoData*) opaque; //cast and give context
int bytesToCopy=0;
int bytesSoFar = mVid->lastByteIndex;
int bytesRemaining = mVid->bytes - mVid->lastByteIndex;
if(bytesRemaining >= len) bytesToCopy = len; //at least as many bytes remaining as requested
else if (bytesRemaining < len) bytesToCopy = bytesRemaining; //less that requested number of bytes remaining
else return 0; // no bytes left to copy
char *start = mVid->video;
std::memcpy(buf,&start[mVid->lastByteIndex], bytesToCopy); //copy bytes requested to buffer.
mVid->lastByteIndex = mVid->lastByteIndex + bytesToCopy; //increment bytes read count
return bytesToCopy;
}
此处请求的是一个打开回调的示例:
int VideoPlayer::memVideo_open(void* opaque, void** datap, uint64_t* sizep)
{
//TODO: get mutex on MemVideoData object pointed to by opaque
MemVideoData *mVid = static_cast<MemVideoData*> (opaque); //cast opaque to our video state struct
*sizep = (uint64_t) mVid->bytesTotal; //set stream length
*datap = mVid; // point to entire object. Think this was intended to point to the raw video data but we use the MemVideoData object in read() and seek()
mVid->lastByteReadIndex=0;
return 0;
}
看看下面我的 Qt 示例,它有效。实际上我不想读取整个文件并将其存储在 ram 中。所以我实现了 MediaDescriptor,因为我将在其中实现解密逻辑以读取加密文件。顺便说一句,我使用了 libvlc 3.0.6 x64 预构建库,看起来运行良好。
MediaDescriptor.h
#pragma once
#include <QObject>
#include <fstream>
class MediaDescriptor : public QObject
{
Q_OBJECT
public:
MediaDescriptor(QString mediaFilePath);
~MediaDescriptor();
bool tryOpen();
uint64_t getMediaLength();
uint64_t getMediaBytes(unsigned char *buffer, uint64_t length);
void setSeek(uint64_t seek);
private:
QString m_mediaFilePath;
std::ifstream *m_mediaFile;
uint64_t m_mediaLength;
uint64_t m_seek;
};
MediaDescriptor.cpp
#include "MediaDescriptor.h"
MediaDescriptor::MediaDescriptor(QString mediaFilePath)
: m_mediaFilePath(mediaFilePath), m_mediaFile(nullptr), m_mediaLength(0), m_seek(0)
{
}
MediaDescriptor::~MediaDescriptor()
{
if (m_mediaFile)
{
m_mediaFile->close();
delete m_mediaFile;
}
}
bool MediaDescriptor::tryOpen()
{
m_mediaFile = new std::ifstream(m_mediaFilePath.toStdString().c_str(), std::ios::binary | std::ios::ate);
if (m_mediaFile->is_open())
{
m_mediaFile->seekg(0, m_mediaFile->end);
m_mediaLength = m_mediaFile->tellg();
return true;
}
delete m_mediaFile;
return false;
}
uint64_t MediaDescriptor::getMediaLength()
{
return m_mediaLength;
}
uint64_t MediaDescriptor::getMediaBytes(unsigned char *buffer, uint64_t length)
{
// to do: decrytion logic
if (m_mediaFile->is_open())
{
uint64_t len = length;
if (m_seek + len > m_mediaLength)
len = (size_t)(m_mediaLength - m_seek);
if (len > 0)
{
m_mediaFile->seekg(m_seek);
m_mediaFile->read((char*)&buffer[0], len);
m_seek += len;
}
return len;
}
return -1;
}
void MediaDescriptor::setSeek(uint64_t seek)
{
m_seek = seek;
}
VLCHelper.h
#pragma once
#include <QObject>
#include <QWidget>
#include <QTime>
#include <mutex>
#include "vlc/vlc.h"
#include "MediaDescriptor.h"
class VLCHelper : public QObject
{
Q_OBJECT
public:
~VLCHelper();
static VLCHelper& getInstance();
int vlcMediaOpenCallback(void* opaque, void** datap, uint64_t* sizep);
int vlcMediaReadCallback(void *opaque, unsigned char* buf, size_t len);
int vlcMediaSeekCallback(void *opaque, uint64_t offset);
void vlcMediaCloseCallback(void *opaque);
void initMedia(MediaDescriptor &mediaDescriptor, QWidget *output = nullptr, int volume = 100, bool repeat = false);
void destroyMedia();
bool isMediaValid();
Q_INVOKABLE void playPauseMedia(bool play);
bool isMediaPlaying();
Q_INVOKABLE void stopMedia();
void setRepeatMedia(bool repeat);
bool getRepeatMedia();
void setMediaPosition(float position);
float getMediaPosition();
QTime getMediaTime();
QTime getMediaTotalTime();
void setMediaVolume(int volume);
int getMediaVolume();
signals:
void mediaEOFReached();
void error(QString error);
private:
VLCHelper();
std::mutex m_callbackMutex;
libvlc_instance_t *m_vlcInstance;
libvlc_media_t *m_vlcMedia;
libvlc_media_player_t *m_vlcMediaPlayer;
bool m_repeat;
bool m_stopRequested;
MediaDescriptor *m_mediaDescriptor;
QWidget *m_output;
};
VLCHelper.cpp
#include "VLCHelper.h"
#pragma region Callback Wrappers
extern "C" int vlcMediaOpenCallbackGateway(void* opaque, void** datap, uint64_t* sizep)
{
return VLCHelper::getInstance().vlcMediaOpenCallback(opaque, datap, sizep);
}
extern "C" int vlcMediaReadCallbackGateway(void *opaque, unsigned char* buf, size_t len)
{
return VLCHelper::getInstance().vlcMediaReadCallback(opaque, buf, len);
}
extern "C" int vlcMediaSeekCallbackGateway(void *opaque, uint64_t offset)
{
return VLCHelper::getInstance().vlcMediaSeekCallback(opaque, offset);
}
extern "C" void vlcMediaCloseCallbackGateway(void *opaque)
{
VLCHelper::getInstance().vlcMediaCloseCallback(opaque);
}
#pragma endregion
VLCHelper::VLCHelper()
: QObject(nullptr),
m_vlcInstance(nullptr),
m_vlcMedia(nullptr),
m_vlcMediaPlayer(nullptr),
m_repeat(false),
m_stopRequested(false)
{
}
VLCHelper::~VLCHelper()
{
}
VLCHelper& VLCHelper::getInstance()
{
static VLCHelper ins;
return ins;
}
void VLCHelper::initMedia(MediaDescriptor &mediaDescriptor, QWidget *output, int volume, bool repeat)
{
m_mediaDescriptor = &mediaDescriptor;
m_output = output;
m_repeat = repeat;
m_vlcInstance = libvlc_new(0, NULL);
m_vlcMedia = libvlc_media_new_callbacks(
m_vlcInstance,
vlcMediaOpenCallbackGateway,
vlcMediaReadCallbackGateway,
vlcMediaSeekCallbackGateway,
vlcMediaCloseCallbackGateway,
0
);
m_vlcMediaPlayer = libvlc_media_player_new_from_media(m_vlcMedia);
#if defined(Q_OS_WIN)
libvlc_media_player_set_hwnd(m_vlcMediaPlayer, output ? (void*)output->winId() : nullptr);
#elif defined(Q_OS_MAC)
libvlc_media_player_set_nsobject(m_vlcMediaPlayer, output ? (void*)output->winId() : nullptr);
#elif defined(Q_OS_LINUX)
libvlc_media_player_set_xwindow(m_vlcMediaPlayer, output ? (void*)output->winId() : nullptr);
#endif
libvlc_audio_set_volume(m_vlcMediaPlayer, volume);
m_mediaDescriptor->setSeek(0);
}
void VLCHelper::destroyMedia()
{
if (!m_vlcInstance)
return;
if (m_vlcMediaPlayer)
{
libvlc_media_player_stop(m_vlcMediaPlayer);
libvlc_media_player_release(m_vlcMediaPlayer);
m_vlcMediaPlayer = nullptr;
}
libvlc_release(m_vlcInstance);
m_vlcInstance = nullptr;
}
bool VLCHelper::isMediaValid()
{
return m_vlcInstance && m_vlcMedia && m_vlcMediaPlayer;
}
void VLCHelper::playPauseMedia(bool play)
{
m_stopRequested = false;
if (isMediaValid())
play ? libvlc_media_player_play(m_vlcMediaPlayer) : libvlc_media_player_pause(m_vlcMediaPlayer);
else
emit error("TO DO");
}
bool VLCHelper::isMediaPlaying()
{
if (isMediaValid())
return libvlc_media_player_is_playing(m_vlcMediaPlayer);
return false;
}
void VLCHelper::stopMedia()
{
m_stopRequested = true;
if (isMediaValid())
libvlc_media_player_stop(m_vlcMediaPlayer);
else
emit error("TO DO");
}
void VLCHelper::setRepeatMedia(bool repeat)
{
m_repeat = repeat;
}
bool VLCHelper::getRepeatMedia()
{
return m_repeat;
}
void VLCHelper::setMediaPosition(float position)
{
if (isMediaValid())
libvlc_media_player_set_position(m_vlcMediaPlayer, position);
else
emit error("TO DO");
}
float VLCHelper::getMediaPosition()
{
if (isMediaValid())
return libvlc_media_player_get_position(m_vlcMediaPlayer);
else
emit error("TO DO");
return -1.0;
}
QTime VLCHelper::getMediaTime()
{
if (isMediaValid())
return QTime::fromMSecsSinceStartOfDay((int)libvlc_media_player_get_time(m_vlcMediaPlayer));
else
emit error("TO DO");
return QTime();
}
QTime VLCHelper::getMediaTotalTime()
{
if (isMediaValid())
return QTime::fromMSecsSinceStartOfDay((int)libvlc_media_player_get_length(m_vlcMediaPlayer));
else
emit error("TO DO");
return QTime();
}
void VLCHelper::setMediaVolume(int volume)
{
if (isMediaValid())
libvlc_audio_set_volume(m_vlcMediaPlayer, volume);
else
emit error("TO DO");
}
int VLCHelper::getMediaVolume()
{
if (isMediaValid())
return libvlc_audio_get_volume(m_vlcMediaPlayer);
else
emit error("TO DO");
return -1;
}
int VLCHelper::vlcMediaOpenCallback(void* opaque, void** datap, uint64_t* sizep)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
// optional, if comment out libvlc will trigger the 'vlcMediaReadCallback' as more often but for less buffer length
*sizep = m_mediaDescriptor->getMediaLength();
return 0;
}
int VLCHelper::vlcMediaReadCallback(void *opaque, unsigned char* buf, size_t len)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
return m_mediaDescriptor->getMediaBytes(buf, len);
}
int VLCHelper::vlcMediaSeekCallback(void *opaque, uint64_t offset)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
// optional, but important for some media types which holds meta data end of the file, for example: .mp4
m_mediaDescriptor->setSeek(offset);
return 0;
}
void VLCHelper::vlcMediaCloseCallback(void *opaque)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
m_mediaDescriptor->setSeek(0);
if (!m_stopRequested)
{
emit mediaEOFReached();
QMetaObject::invokeMethod(&getInstance(), "stopMedia");
if(m_repeat)
QMetaObject::invokeMethod(&getInstance(), "playPauseMedia", Q_ARG(bool, true));
}
}
以及播放器小部件中的用法:
VLCHelper::getInstance().initMedia(*m_mediaDescriptor, ui.frame_video);
connect(&VLCHelper::getInstance(), SIGNAL(mediaEOFReached()), this, SLOT(vlcMediaEOFReached()));
connect(&VLCHelper::getInstance(), SIGNAL(error(QString)), this, SLOT(vlcError(QString)));
...
void PlayerWidget::on_pushButton_media_play_pause_clicked()
{
VLCHelper::getInstance().playPauseMedia(!VLCHelper::getInstance().isMediaPlaying());
}
void PlayerWidget::on_pushButton_media_stop_clicked()
{
VLCHelper::getInstance().stopMedia();
}
void PlayerWidget::timer_timeout()
{
bool isValid = VLCHelper::getInstance().isMediaValid();
if (isValid)
{
if (VLCHelper::getInstance().isMediaPlaying())
{
// update media position
ui.horizontalSlider_media_position->blockSignals(true);
ui.horizontalSlider_media_position->setValue((int)(VLCHelper::getInstance().getMediaPosition() * 1000.0f));
ui.horizontalSlider_media_position->blockSignals(false);
// update media volume
ui.horizontalSlider_media_volume->blockSignals(true);
ui.horizontalSlider_media_volume->setValue(VLCHelper::getInstance().getMediaVolume());
ui.horizontalSlider_media_volume->blockSignals(false);
// update media time
ui.label_media_time->setText(VLCHelper::getInstance().getMediaTime().toString());
// update media total time
ui.label_media_time_total->setText(VLCHelper::getInstance().getMediaTotalTime().toString());
}
}
ui.horizontalSlider_media_volume->setEnabled(isValid);
ui.pushButton_media_stop->setEnabled(isValid);
ui.pushButton_media_repeat->setEnabled(isValid);
}
每当您将 stream 传递给 libvlc 时,它都需要执行 open,read,seek,close 流操作。
以下是执行此操作的函数:
using namespace std;
int Open(void* opaque, void** datap, uint64_t* sizep)
{
ifstream *file = (ifstream *)(opaque);
file->seekg(0, file->beg);
*sizep = file->tellg();
*datap = opaque;
return 0;
}
SSIZE_T Read(void *opaque, unsigned char* buffer, size_t length)
{
ifstream *file = (ifstream *)(opaque);
file->read((char *)(buffer), length);
return file->gcount();
}
int Seek(void *opaque, uint64_t offset)
{
((ifstream *)(opaque))->seekg(offset); //Seek Stream to offset
return 0; //Success
}
void Close(void *opaque)
{
((ifstream *)(opaque))->close();
}
然后像这样调用 libvlc_media_new_callbacks:
const char *FilePath = "C:\folder\video.mp4";
ifstream *FileStream = new std::ifstream(FilePath, std::ios::binary | std::ios::ate);
Libvlc_Media = libvlc_media_new_callbacks(
Instance,
Open,
Read,
Seek,
Close,
FileStream
);
我是在@cube45 的帮助下实现的。阅读整个讨论 here. Before login & join libvlc server. More info here。
我有一个加载到内存中的 h264 视频文件,我尝试使用参数 "imem-cat=4" 使用 imem 播放它,这样 vlc 将使用访问模块来解复用视频,然后 vlc 启动并成功接收我的 imem 参数:
[0x7f38a0000e28] access_imem demux debug: Using get(0x404e1d), release(0x404e91), data(0x7fff5b4a9430), cookie("IMEM")
这个类别也意味着我不必提供DTS和PTS。 VLC 的 imem 模块没有很好的文档记录,但我在几个地方找到了提示,例如
https://forum.videolan.org/viewtopic.php?t=111917
https://forum.videolan.org/viewtopic.php?f=32&t=93842
Play video using libVLC from memory in python
我的 imem-get 函数只是在第一次调用时将缓冲区指针设置为视频数据,返回 0,在任何进一步调用它时 returns 1 表示没有更多数据:
int MyImemGetCallback (void *data,
const char *cookie,
int64_t *dts,
int64_t *pts,
unsigned *flags,
size_t * bufferSize,
void ** buffer)
{
ImemData* imem = (ImemData*)data;
cookie = imem->cookieString;
if(imem == NULL || imem->allBuffered==true) //indicate all data has been get()ted
return 1;
*buffer = (void*) imem->video;
bufferSize = (size_t*) &(imem->bytes);
imem->allBuffered=true;
return 0;
}
不幸的是,在第一次调用后我收到以下错误:
[0x189cb18] main input debug: Creating an input for 'imem://'
[0x189cb18] main input debug: using timeshift granularity of 50 MiB, in path '/tmp'
[0x189cb18] main input debug: `imem://' gives access `imem' demux `' path `'
[0x189cb18] main input debug: creating demux: access='imem' demux='' location='' file='(null)'
[0x7f2808000e28] main demux debug: looking for access_demux module matching "imem": 20 candidates
[0x7f2808000e28] access_imem demux debug: Using get(0x404e1d), release(0x404e91), data(0x7ffe0da3b940), cookie("h264")
[0x7f2808000e28] main demux debug: no access_demux modules matched
[0x189cb18] main input debug: creating access 'imem' location='', path='(null)'
[0x7f2808001958] main access debug: looking for access module matching "imem": 25 candidates
[0x7f2808001958] access_imem access debug: Using get(0x404e1d), release(0x404e91), data(0x7ffe0da3b940), cookie("h264")
[0x7f2808001958] main access debug: using access module "access_imem"
[0x7f2808000e28] main stream debug: Using block method for AStream*
[0x7f2808000e28] main stream debug: starting pre-buffering
[0x7f2808000e28] main stream error: cannot pre fill buffer
[0x7f2808001958] main access debug: removing module "access_imem"
[0x189cb18] main input warning: cannot create a stream_t from access
[0x17d7298] main libvlc debug: removing all interfaces
[0x17d7298] main libvlc debug: exiting
[0x17d7298] main libvlc debug: no exit handler
[0x17d7298] main libvlc debug: removing stats
出于某种原因,vlc 似乎无法访问视频数据,但错误消息不是很有用,通常指的是网络流而不是内存位置。
有没有人以这种方式成功使用 imem 或对可能出现的问题有任何想法?视频在 VLC 中完美地从磁盘播放。 感谢您的帮助。
编辑
道具界面好像不支持这样玩。但是,libVLC 提供了 libvlc_media_t 和 livblc_media_new_callbacks 这可能会让我实现我想要的。如果我成功了,我会报告。
所以我无法让 Imem 工作,但是在 VLC 论坛上我被指向了 3.0.0 版中可用的新 API。我不得不删除我当前安装的 vlc 和 libvlc-dev,并将 VLC 每日构建 PPA 添加到 Ubuntu 安装,然后安装这些版本。
API是libvlc_media_new_callbacks:
LIBVLC_API libvlc_media_t * libvlc_media_new_callbacks
(
libvlc_instance_t *instance,
libvlc_media_open_cb open_cb,
libvlc_media_read_cb read_cb,
libvlc_media_seek_cb seek_cb,
libvlc_media_close_cb close_cb,
void *opaque
);
您必须实现每个回调才能让 VLC 访问内存中的流。尽管文档指出不需要实现 seek() 回调,但没有它我无法播放 h264 视频。
open() 回调应该传递一个指向您的视频数据的指针,我推荐一个容器 class 这样您就可以存储读取的最后一个字节的索引。
read() 回调用于将 len 字节读入传递指针的缓冲区。在这里,您可以将 len 或更少的字节写入缓冲区和 return 复制的字节数,阻塞直到准备好一些字节,return 0 表示 EOF 或 -1 表示错误。
seek() 回调用于设置下一个 read() 发生的字节索引。
最后 close() 没有 return 任何东西,用于整理。
这里是一个 read() 实现的例子:
class MemVideoData
{
public:
MemVideoData(char *data, int data_bytes) : video(data), bytes(data_bytes), lastByteIndex(0) {} //init
~MemVideoData() {}
char* video; //pointer to video in memory
int bytes;
int lastByteIndex;
};
ssize_t memVideo_read(void *opaque, unsigned char* buf, size_t len)
{
//TODO: block if not end of stream but no bytes available
MemVideoData *mVid = (MemVideoData*) opaque; //cast and give context
int bytesToCopy=0;
int bytesSoFar = mVid->lastByteIndex;
int bytesRemaining = mVid->bytes - mVid->lastByteIndex;
if(bytesRemaining >= len) bytesToCopy = len; //at least as many bytes remaining as requested
else if (bytesRemaining < len) bytesToCopy = bytesRemaining; //less that requested number of bytes remaining
else return 0; // no bytes left to copy
char *start = mVid->video;
std::memcpy(buf,&start[mVid->lastByteIndex], bytesToCopy); //copy bytes requested to buffer.
mVid->lastByteIndex = mVid->lastByteIndex + bytesToCopy; //increment bytes read count
return bytesToCopy;
}
此处请求的是一个打开回调的示例:
int VideoPlayer::memVideo_open(void* opaque, void** datap, uint64_t* sizep)
{
//TODO: get mutex on MemVideoData object pointed to by opaque
MemVideoData *mVid = static_cast<MemVideoData*> (opaque); //cast opaque to our video state struct
*sizep = (uint64_t) mVid->bytesTotal; //set stream length
*datap = mVid; // point to entire object. Think this was intended to point to the raw video data but we use the MemVideoData object in read() and seek()
mVid->lastByteReadIndex=0;
return 0;
}
看看下面我的 Qt 示例,它有效。实际上我不想读取整个文件并将其存储在 ram 中。所以我实现了 MediaDescriptor,因为我将在其中实现解密逻辑以读取加密文件。顺便说一句,我使用了 libvlc 3.0.6 x64 预构建库,看起来运行良好。
MediaDescriptor.h
#pragma once
#include <QObject>
#include <fstream>
class MediaDescriptor : public QObject
{
Q_OBJECT
public:
MediaDescriptor(QString mediaFilePath);
~MediaDescriptor();
bool tryOpen();
uint64_t getMediaLength();
uint64_t getMediaBytes(unsigned char *buffer, uint64_t length);
void setSeek(uint64_t seek);
private:
QString m_mediaFilePath;
std::ifstream *m_mediaFile;
uint64_t m_mediaLength;
uint64_t m_seek;
};
MediaDescriptor.cpp
#include "MediaDescriptor.h"
MediaDescriptor::MediaDescriptor(QString mediaFilePath)
: m_mediaFilePath(mediaFilePath), m_mediaFile(nullptr), m_mediaLength(0), m_seek(0)
{
}
MediaDescriptor::~MediaDescriptor()
{
if (m_mediaFile)
{
m_mediaFile->close();
delete m_mediaFile;
}
}
bool MediaDescriptor::tryOpen()
{
m_mediaFile = new std::ifstream(m_mediaFilePath.toStdString().c_str(), std::ios::binary | std::ios::ate);
if (m_mediaFile->is_open())
{
m_mediaFile->seekg(0, m_mediaFile->end);
m_mediaLength = m_mediaFile->tellg();
return true;
}
delete m_mediaFile;
return false;
}
uint64_t MediaDescriptor::getMediaLength()
{
return m_mediaLength;
}
uint64_t MediaDescriptor::getMediaBytes(unsigned char *buffer, uint64_t length)
{
// to do: decrytion logic
if (m_mediaFile->is_open())
{
uint64_t len = length;
if (m_seek + len > m_mediaLength)
len = (size_t)(m_mediaLength - m_seek);
if (len > 0)
{
m_mediaFile->seekg(m_seek);
m_mediaFile->read((char*)&buffer[0], len);
m_seek += len;
}
return len;
}
return -1;
}
void MediaDescriptor::setSeek(uint64_t seek)
{
m_seek = seek;
}
VLCHelper.h
#pragma once
#include <QObject>
#include <QWidget>
#include <QTime>
#include <mutex>
#include "vlc/vlc.h"
#include "MediaDescriptor.h"
class VLCHelper : public QObject
{
Q_OBJECT
public:
~VLCHelper();
static VLCHelper& getInstance();
int vlcMediaOpenCallback(void* opaque, void** datap, uint64_t* sizep);
int vlcMediaReadCallback(void *opaque, unsigned char* buf, size_t len);
int vlcMediaSeekCallback(void *opaque, uint64_t offset);
void vlcMediaCloseCallback(void *opaque);
void initMedia(MediaDescriptor &mediaDescriptor, QWidget *output = nullptr, int volume = 100, bool repeat = false);
void destroyMedia();
bool isMediaValid();
Q_INVOKABLE void playPauseMedia(bool play);
bool isMediaPlaying();
Q_INVOKABLE void stopMedia();
void setRepeatMedia(bool repeat);
bool getRepeatMedia();
void setMediaPosition(float position);
float getMediaPosition();
QTime getMediaTime();
QTime getMediaTotalTime();
void setMediaVolume(int volume);
int getMediaVolume();
signals:
void mediaEOFReached();
void error(QString error);
private:
VLCHelper();
std::mutex m_callbackMutex;
libvlc_instance_t *m_vlcInstance;
libvlc_media_t *m_vlcMedia;
libvlc_media_player_t *m_vlcMediaPlayer;
bool m_repeat;
bool m_stopRequested;
MediaDescriptor *m_mediaDescriptor;
QWidget *m_output;
};
VLCHelper.cpp
#include "VLCHelper.h"
#pragma region Callback Wrappers
extern "C" int vlcMediaOpenCallbackGateway(void* opaque, void** datap, uint64_t* sizep)
{
return VLCHelper::getInstance().vlcMediaOpenCallback(opaque, datap, sizep);
}
extern "C" int vlcMediaReadCallbackGateway(void *opaque, unsigned char* buf, size_t len)
{
return VLCHelper::getInstance().vlcMediaReadCallback(opaque, buf, len);
}
extern "C" int vlcMediaSeekCallbackGateway(void *opaque, uint64_t offset)
{
return VLCHelper::getInstance().vlcMediaSeekCallback(opaque, offset);
}
extern "C" void vlcMediaCloseCallbackGateway(void *opaque)
{
VLCHelper::getInstance().vlcMediaCloseCallback(opaque);
}
#pragma endregion
VLCHelper::VLCHelper()
: QObject(nullptr),
m_vlcInstance(nullptr),
m_vlcMedia(nullptr),
m_vlcMediaPlayer(nullptr),
m_repeat(false),
m_stopRequested(false)
{
}
VLCHelper::~VLCHelper()
{
}
VLCHelper& VLCHelper::getInstance()
{
static VLCHelper ins;
return ins;
}
void VLCHelper::initMedia(MediaDescriptor &mediaDescriptor, QWidget *output, int volume, bool repeat)
{
m_mediaDescriptor = &mediaDescriptor;
m_output = output;
m_repeat = repeat;
m_vlcInstance = libvlc_new(0, NULL);
m_vlcMedia = libvlc_media_new_callbacks(
m_vlcInstance,
vlcMediaOpenCallbackGateway,
vlcMediaReadCallbackGateway,
vlcMediaSeekCallbackGateway,
vlcMediaCloseCallbackGateway,
0
);
m_vlcMediaPlayer = libvlc_media_player_new_from_media(m_vlcMedia);
#if defined(Q_OS_WIN)
libvlc_media_player_set_hwnd(m_vlcMediaPlayer, output ? (void*)output->winId() : nullptr);
#elif defined(Q_OS_MAC)
libvlc_media_player_set_nsobject(m_vlcMediaPlayer, output ? (void*)output->winId() : nullptr);
#elif defined(Q_OS_LINUX)
libvlc_media_player_set_xwindow(m_vlcMediaPlayer, output ? (void*)output->winId() : nullptr);
#endif
libvlc_audio_set_volume(m_vlcMediaPlayer, volume);
m_mediaDescriptor->setSeek(0);
}
void VLCHelper::destroyMedia()
{
if (!m_vlcInstance)
return;
if (m_vlcMediaPlayer)
{
libvlc_media_player_stop(m_vlcMediaPlayer);
libvlc_media_player_release(m_vlcMediaPlayer);
m_vlcMediaPlayer = nullptr;
}
libvlc_release(m_vlcInstance);
m_vlcInstance = nullptr;
}
bool VLCHelper::isMediaValid()
{
return m_vlcInstance && m_vlcMedia && m_vlcMediaPlayer;
}
void VLCHelper::playPauseMedia(bool play)
{
m_stopRequested = false;
if (isMediaValid())
play ? libvlc_media_player_play(m_vlcMediaPlayer) : libvlc_media_player_pause(m_vlcMediaPlayer);
else
emit error("TO DO");
}
bool VLCHelper::isMediaPlaying()
{
if (isMediaValid())
return libvlc_media_player_is_playing(m_vlcMediaPlayer);
return false;
}
void VLCHelper::stopMedia()
{
m_stopRequested = true;
if (isMediaValid())
libvlc_media_player_stop(m_vlcMediaPlayer);
else
emit error("TO DO");
}
void VLCHelper::setRepeatMedia(bool repeat)
{
m_repeat = repeat;
}
bool VLCHelper::getRepeatMedia()
{
return m_repeat;
}
void VLCHelper::setMediaPosition(float position)
{
if (isMediaValid())
libvlc_media_player_set_position(m_vlcMediaPlayer, position);
else
emit error("TO DO");
}
float VLCHelper::getMediaPosition()
{
if (isMediaValid())
return libvlc_media_player_get_position(m_vlcMediaPlayer);
else
emit error("TO DO");
return -1.0;
}
QTime VLCHelper::getMediaTime()
{
if (isMediaValid())
return QTime::fromMSecsSinceStartOfDay((int)libvlc_media_player_get_time(m_vlcMediaPlayer));
else
emit error("TO DO");
return QTime();
}
QTime VLCHelper::getMediaTotalTime()
{
if (isMediaValid())
return QTime::fromMSecsSinceStartOfDay((int)libvlc_media_player_get_length(m_vlcMediaPlayer));
else
emit error("TO DO");
return QTime();
}
void VLCHelper::setMediaVolume(int volume)
{
if (isMediaValid())
libvlc_audio_set_volume(m_vlcMediaPlayer, volume);
else
emit error("TO DO");
}
int VLCHelper::getMediaVolume()
{
if (isMediaValid())
return libvlc_audio_get_volume(m_vlcMediaPlayer);
else
emit error("TO DO");
return -1;
}
int VLCHelper::vlcMediaOpenCallback(void* opaque, void** datap, uint64_t* sizep)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
// optional, if comment out libvlc will trigger the 'vlcMediaReadCallback' as more often but for less buffer length
*sizep = m_mediaDescriptor->getMediaLength();
return 0;
}
int VLCHelper::vlcMediaReadCallback(void *opaque, unsigned char* buf, size_t len)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
return m_mediaDescriptor->getMediaBytes(buf, len);
}
int VLCHelper::vlcMediaSeekCallback(void *opaque, uint64_t offset)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
// optional, but important for some media types which holds meta data end of the file, for example: .mp4
m_mediaDescriptor->setSeek(offset);
return 0;
}
void VLCHelper::vlcMediaCloseCallback(void *opaque)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
m_mediaDescriptor->setSeek(0);
if (!m_stopRequested)
{
emit mediaEOFReached();
QMetaObject::invokeMethod(&getInstance(), "stopMedia");
if(m_repeat)
QMetaObject::invokeMethod(&getInstance(), "playPauseMedia", Q_ARG(bool, true));
}
}
以及播放器小部件中的用法:
VLCHelper::getInstance().initMedia(*m_mediaDescriptor, ui.frame_video);
connect(&VLCHelper::getInstance(), SIGNAL(mediaEOFReached()), this, SLOT(vlcMediaEOFReached()));
connect(&VLCHelper::getInstance(), SIGNAL(error(QString)), this, SLOT(vlcError(QString)));
...
void PlayerWidget::on_pushButton_media_play_pause_clicked()
{
VLCHelper::getInstance().playPauseMedia(!VLCHelper::getInstance().isMediaPlaying());
}
void PlayerWidget::on_pushButton_media_stop_clicked()
{
VLCHelper::getInstance().stopMedia();
}
void PlayerWidget::timer_timeout()
{
bool isValid = VLCHelper::getInstance().isMediaValid();
if (isValid)
{
if (VLCHelper::getInstance().isMediaPlaying())
{
// update media position
ui.horizontalSlider_media_position->blockSignals(true);
ui.horizontalSlider_media_position->setValue((int)(VLCHelper::getInstance().getMediaPosition() * 1000.0f));
ui.horizontalSlider_media_position->blockSignals(false);
// update media volume
ui.horizontalSlider_media_volume->blockSignals(true);
ui.horizontalSlider_media_volume->setValue(VLCHelper::getInstance().getMediaVolume());
ui.horizontalSlider_media_volume->blockSignals(false);
// update media time
ui.label_media_time->setText(VLCHelper::getInstance().getMediaTime().toString());
// update media total time
ui.label_media_time_total->setText(VLCHelper::getInstance().getMediaTotalTime().toString());
}
}
ui.horizontalSlider_media_volume->setEnabled(isValid);
ui.pushButton_media_stop->setEnabled(isValid);
ui.pushButton_media_repeat->setEnabled(isValid);
}
每当您将 stream 传递给 libvlc 时,它都需要执行 open,read,seek,close 流操作。
以下是执行此操作的函数:
using namespace std;
int Open(void* opaque, void** datap, uint64_t* sizep)
{
ifstream *file = (ifstream *)(opaque);
file->seekg(0, file->beg);
*sizep = file->tellg();
*datap = opaque;
return 0;
}
SSIZE_T Read(void *opaque, unsigned char* buffer, size_t length)
{
ifstream *file = (ifstream *)(opaque);
file->read((char *)(buffer), length);
return file->gcount();
}
int Seek(void *opaque, uint64_t offset)
{
((ifstream *)(opaque))->seekg(offset); //Seek Stream to offset
return 0; //Success
}
void Close(void *opaque)
{
((ifstream *)(opaque))->close();
}
然后像这样调用 libvlc_media_new_callbacks:
const char *FilePath = "C:\folder\video.mp4";
ifstream *FileStream = new std::ifstream(FilePath, std::ios::binary | std::ios::ate);
Libvlc_Media = libvlc_media_new_callbacks(
Instance,
Open,
Read,
Seek,
Close,
FileStream
);
我是在@cube45 的帮助下实现的。阅读整个讨论 here. Before login & join libvlc server. More info here。