Player now uses queue to buffering decoded frames
This commit is contained in:
parent
23fd1db633
commit
36d3ab2a6e
@ -4,8 +4,7 @@
|
|||||||
#include <SDL_thread.h>
|
#include <SDL_thread.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <chrono>
|
#include <future>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace ff = ffcpp;
|
namespace ff = ffcpp;
|
||||||
|
|
||||||
@ -23,6 +22,7 @@ private:
|
|||||||
SDLWindowPtr _wnd;
|
SDLWindowPtr _wnd;
|
||||||
SDLRendererPtr _renderer;
|
SDLRendererPtr _renderer;
|
||||||
SDLTexturePtr _texture;
|
SDLTexturePtr _texture;
|
||||||
|
std::packaged_task<void()> _renderTask;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SDLWindow(): _wnd(nullptr, SDL_DestroyWindow), _renderer(nullptr, SDL_DestroyRenderer), _texture(nullptr, SDL_DestroyTexture) {
|
SDLWindow(): _wnd(nullptr, SDL_DestroyWindow), _renderer(nullptr, SDL_DestroyRenderer), _texture(nullptr, SDL_DestroyTexture) {
|
||||||
@ -39,19 +39,27 @@ public:
|
|||||||
if(!_texture) throw std::runtime_error("Error creating SDL texture");
|
if(!_texture) throw std::runtime_error("Error creating SDL texture");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleEvents() {
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
SDL_WaitEvent(&event);
|
||||||
|
switch(event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
return;
|
||||||
|
case SDL_USEREVENT: {
|
||||||
|
_renderTask();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual AVPixelFormat getPixelFormat() const noexcept override {
|
virtual AVPixelFormat getPixelFormat() const noexcept override {
|
||||||
return AV_PIX_FMT_YUV420P;
|
return AV_PIX_FMT_YUV420P;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int getWidth() const noexcept override {
|
|
||||||
return WINDOW_WIDTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual int getHeight() const noexcept override {
|
|
||||||
return WINDOW_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void drawFrame(void* pixelsData, int pitch) override {
|
virtual void drawFrame(void* pixelsData, int pitch) override {
|
||||||
std::cout << "drawing frame" << std::endl;
|
std::cout << "drawing frame" << std::endl;
|
||||||
SDL_UpdateTexture(_texture.get(), nullptr, pixelsData, pitch);
|
SDL_UpdateTexture(_texture.get(), nullptr, pixelsData, pitch);
|
||||||
@ -61,19 +69,31 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawPlanarYUVFrame(const void *yPlane, const void *uPlane, const void *vPlane, int yPitch, int uPitch, int vPitch) override {
|
virtual void drawPlanarYUVFrame(const void *yPlane, const void *uPlane, const void *vPlane, int yPitch, int uPitch, int vPitch) override {
|
||||||
SDL_UpdateYUVTexture(_texture.get(), nullptr, (const uint8_t*)yPlane, yPitch, (const uint8_t*)uPlane, uPitch, (const uint8_t*)vPlane, vPitch);
|
_renderTask = std::packaged_task<void()>([=]{
|
||||||
SDL_RenderClear(_renderer.get());
|
SDL_UpdateYUVTexture(_texture.get(), nullptr, (const uint8_t*)yPlane, yPitch, (const uint8_t*)uPlane, uPitch, (const uint8_t*)vPlane, vPitch);
|
||||||
SDL_RenderCopy(_renderer.get(), _texture.get(), nullptr, nullptr);
|
SDL_RenderClear(_renderer.get());
|
||||||
SDL_RenderPresent(_renderer.get());
|
SDL_RenderCopy(_renderer.get(), _texture.get(), nullptr, nullptr);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(40));
|
SDL_RenderPresent(_renderer.get());
|
||||||
|
});
|
||||||
|
auto future = _renderTask.get_future();
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
event.type = SDL_USEREVENT;
|
||||||
|
int res = SDL_PushEvent(&event);
|
||||||
|
|
||||||
|
future.get();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
auto wnd = std::make_shared<SDLWindow>();
|
auto wnd = std::make_shared<SDLWindow>();
|
||||||
|
|
||||||
ff::Player player(wnd);
|
ff::Player player(wnd);
|
||||||
player.setMedia(argv[1]);
|
player.setMedia(argv[1]);
|
||||||
|
player.setVideoSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
player.play();
|
player.play();
|
||||||
|
|
||||||
|
wnd->handleEvents();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ namespace ffcpp {
|
|||||||
void setHeight(int height);
|
void setHeight(int height);
|
||||||
void setPixelFormat(AVPixelFormat pixelFormat);
|
void setPixelFormat(AVPixelFormat pixelFormat);
|
||||||
|
|
||||||
Frame decode(Packet& packet);
|
FramePtr decode(Packet& packet);
|
||||||
Packet encode(AVFrame* frame);
|
Packet encode(AVFrame* frame);
|
||||||
Frame createAudioFrame() const;
|
Frame createAudioFrame() const;
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,12 @@ extern "C" {
|
|||||||
#include <libavutil/imgutils.h>
|
#include <libavutil/imgutils.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace ffcpp {
|
namespace ffcpp {
|
||||||
|
|
||||||
|
typedef std::shared_ptr<class Frame> FramePtr;
|
||||||
|
|
||||||
class Frame {
|
class Frame {
|
||||||
private:
|
private:
|
||||||
uint8_t* _buffer;
|
uint8_t* _buffer;
|
||||||
@ -23,6 +27,7 @@ namespace ffcpp {
|
|||||||
Frame& operator=(Frame&& frame);
|
Frame& operator=(Frame&& frame);
|
||||||
operator AVFrame*();
|
operator AVFrame*();
|
||||||
operator const AVFrame*() const;
|
operator const AVFrame*() const;
|
||||||
|
AVFrame* nativePtr();
|
||||||
|
|
||||||
void guessPts();
|
void guessPts();
|
||||||
void setPictureType(AVPictureType type);
|
void setPictureType(AVPictureType type);
|
||||||
|
|||||||
@ -2,21 +2,24 @@
|
|||||||
#define PROJECT_PLAYER_H
|
#define PROJECT_PLAYER_H
|
||||||
|
|
||||||
#include "ffcpp/MediaFile.h"
|
#include "ffcpp/MediaFile.h"
|
||||||
|
#include "ffcpp/Scaler.h"
|
||||||
|
#include "TSQueue.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace ffcpp {
|
namespace ffcpp {
|
||||||
|
|
||||||
struct IVideoSink {
|
struct IVideoSink {
|
||||||
virtual AVPixelFormat getPixelFormat() const noexcept = 0;
|
virtual AVPixelFormat getPixelFormat() const noexcept = 0;
|
||||||
virtual int getWidth() const noexcept = 0;
|
|
||||||
virtual int getHeight() const noexcept = 0;
|
|
||||||
virtual void drawFrame(void* pixelsData, int pitch) = 0;
|
virtual void drawFrame(void* pixelsData, int pitch) = 0;
|
||||||
virtual void drawPlanarYUVFrame(const void *yPlane, const void *uPlane, const void *vPlane, int yPitch,
|
virtual void drawPlanarYUVFrame(const void *yPlane, const void *uPlane, const void *vPlane, int yPitch,
|
||||||
int uPitch, int vPitch) = 0;
|
int uPitch, int vPitch) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class PlayerState {
|
enum class PlayerState {
|
||||||
|
Shutdown,
|
||||||
Stopped,
|
Stopped,
|
||||||
Playing,
|
Playing,
|
||||||
Paused
|
Paused
|
||||||
@ -28,18 +31,26 @@ namespace ffcpp {
|
|||||||
std::unique_ptr<MediaFile> _curMedia;
|
std::unique_ptr<MediaFile> _curMedia;
|
||||||
StreamPtr _aStream;
|
StreamPtr _aStream;
|
||||||
StreamPtr _vStream;
|
StreamPtr _vStream;
|
||||||
std::thread _decodeThread;
|
ScalerPtr _scaler;
|
||||||
PlayerState _state;
|
PlayerState _state;
|
||||||
|
|
||||||
|
TSQueue<Frame> _decodedFrames;
|
||||||
|
std::thread _decodeThread;
|
||||||
|
std::thread _vPlayThread;
|
||||||
|
std::mutex _mutex;
|
||||||
|
std::condition_variable _stateCond;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Player(std::shared_ptr<IVideoSink> vSink);
|
Player(std::shared_ptr<IVideoSink> vSink);
|
||||||
~Player();
|
~Player();
|
||||||
|
|
||||||
void setMedia(std::string path);
|
void setMedia(std::string path);
|
||||||
|
void setVideoSize(size_t width, size_t height);
|
||||||
void play();
|
void play();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void decode();
|
void decode();
|
||||||
|
void displayFrames();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ extern "C" {
|
|||||||
|
|
||||||
namespace ffcpp {
|
namespace ffcpp {
|
||||||
|
|
||||||
|
typedef std::shared_ptr<class Scaler> ScalerPtr;
|
||||||
|
|
||||||
class Scaler {
|
class Scaler {
|
||||||
private:
|
private:
|
||||||
SwsContext* _swsContext;
|
SwsContext* _swsContext;
|
||||||
@ -20,7 +22,7 @@ namespace ffcpp {
|
|||||||
public:
|
public:
|
||||||
Scaler(int srcWidth, int srcHeight, AVPixelFormat srcPixFmt, int dstWidth, int dstHeight, AVPixelFormat dstPixFmt);
|
Scaler(int srcWidth, int srcHeight, AVPixelFormat srcPixFmt, int dstWidth, int dstHeight, AVPixelFormat dstPixFmt);
|
||||||
Scaler(CodecPtr decoder, CodecPtr encoder);
|
Scaler(CodecPtr decoder, CodecPtr encoder);
|
||||||
Frame scale(Frame& inFrame);
|
FramePtr scale(FramePtr inFrame);
|
||||||
static bool needScaling(CodecPtr decoder, CodecPtr encoder);
|
static bool needScaling(CodecPtr decoder, CodecPtr encoder);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ namespace ffcpp {
|
|||||||
|
|
||||||
AVRational timeBase() const;
|
AVRational timeBase() const;
|
||||||
void setTimeBase(AVRational timeBase);
|
void setTimeBase(AVRational timeBase);
|
||||||
|
int fps() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Stream(Stream&& stream) noexcept;
|
Stream(Stream&& stream) noexcept;
|
||||||
|
|||||||
@ -45,11 +45,22 @@ namespace ffcpp {
|
|||||||
_readCond.notify_one();
|
_readCond.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void pushOrWait(std::shared_ptr<T> value) {
|
||||||
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
|
if(_queue.size() == _maxSize) {
|
||||||
|
_writeCond.wait(lock, [this]{ return _queue.size() < _maxSize; });
|
||||||
|
}
|
||||||
|
_queue.push(value);
|
||||||
|
_readCond.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<T> waitAndPop() {
|
std::shared_ptr<T> waitAndPop() {
|
||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
_readCond.wait(lock, [this]{ return !_queue.empty(); });
|
_readCond.wait(lock, [this]{ return !_queue.empty(); });
|
||||||
auto res = _queue.front();
|
auto res = _queue.front();
|
||||||
_queue.pop();
|
_queue.pop();
|
||||||
|
_writeCond.notify_one();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +70,7 @@ namespace ffcpp {
|
|||||||
return std::shared_ptr<T>();
|
return std::shared_ptr<T>();
|
||||||
auto res = _queue.front();
|
auto res = _queue.front();
|
||||||
_queue.pop();
|
_queue.pop();
|
||||||
|
_writeCond.notify_one();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +81,7 @@ namespace ffcpp {
|
|||||||
}
|
}
|
||||||
auto res = _queue.front();
|
auto res = _queue.front();
|
||||||
_queue.pop();
|
_queue.pop();
|
||||||
|
_writeCond.notify_one();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -103,18 +103,18 @@ namespace ffcpp {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Frame Codec::decode(Packet &packet) {
|
FramePtr Codec::decode(Packet &packet) {
|
||||||
Frame frame;
|
FramePtr frame = std::make_shared<Frame>();
|
||||||
int gotPicture = 0;
|
int gotPicture = 0;
|
||||||
auto decFunc = (_codecCtx->codec_type == AVMEDIA_TYPE_VIDEO ? avcodec_decode_video2 : avcodec_decode_audio4);
|
auto decFunc = (_codecCtx->codec_type == AVMEDIA_TYPE_VIDEO ? avcodec_decode_video2 : avcodec_decode_audio4);
|
||||||
|
|
||||||
while(!gotPicture) {
|
while(!gotPicture) {
|
||||||
int res = decFunc(_codecCtx, frame, &gotPicture, packet);
|
int res = decFunc(_codecCtx, frame->nativePtr(), &gotPicture, packet);
|
||||||
if(res < 0) throw std::runtime_error("cannot decode packet");
|
if(res < 0) throw std::runtime_error("cannot decode packet");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_codecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
|
if(_codecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||||
frame.guessPts();
|
frame->guessPts();
|
||||||
}
|
}
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
|
|||||||
@ -58,6 +58,10 @@ namespace ffcpp {
|
|||||||
return _frame;
|
return _frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AVFrame* Frame::nativePtr() {
|
||||||
|
return _frame;
|
||||||
|
}
|
||||||
|
|
||||||
void Frame::guessPts() {
|
void Frame::guessPts() {
|
||||||
_frame->pts = av_frame_get_best_effort_timestamp(_frame);
|
_frame->pts = av_frame_get_best_effort_timestamp(_frame);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include "ffcpp/Stream.h"
|
#include "ffcpp/Stream.h"
|
||||||
#include "ffcpp/Scaler.h"
|
#include "ffcpp/Scaler.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
namespace ffcpp {
|
namespace ffcpp {
|
||||||
|
|
||||||
@ -9,46 +10,96 @@ namespace ffcpp {
|
|||||||
_curMedia(nullptr),
|
_curMedia(nullptr),
|
||||||
_aStream(nullptr),
|
_aStream(nullptr),
|
||||||
_vStream(nullptr),
|
_vStream(nullptr),
|
||||||
|
_state(PlayerState::Stopped),
|
||||||
_decodeThread(&Player::decode, this),
|
_decodeThread(&Player::decode, this),
|
||||||
_state(PlayerState::Stopped)
|
_vPlayThread(&Player::displayFrames, this),
|
||||||
|
_decodedFrames(10)
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player::~Player() {
|
Player::~Player() {
|
||||||
|
// _state = PlayerState::Shutdown;
|
||||||
|
// std::cout << "destructor" << std::endl;
|
||||||
|
// _stateCond.notify_all();
|
||||||
|
//
|
||||||
|
// if(_decodeThread.joinable()) {
|
||||||
|
// _decodeThread.join();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if(_vPlayThread.joinable()) {
|
||||||
|
// _vPlayThread.join();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::setMedia(std::string path) {
|
void Player::setMedia(std::string path) {
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
_curMedia = std::make_unique<MediaFile>(path, Mode::Read);
|
_curMedia = std::make_unique<MediaFile>(path, Mode::Read);
|
||||||
|
|
||||||
_vStream = _curMedia->videoStream();
|
_vStream = _curMedia->videoStream();
|
||||||
_aStream = _curMedia->audioStream();
|
_aStream = _curMedia->audioStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::setVideoSize(size_t width, size_t height) {
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
_scaler = std::make_shared<Scaler>(_vStream->codec()->width(), _vStream->codec()->height(), _vStream->codec()->pixelFormat(),
|
||||||
|
width, height, _vSink->getPixelFormat());
|
||||||
|
}
|
||||||
|
|
||||||
void Player::play() {
|
void Player::play() {
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
if(!_curMedia)
|
if(!_curMedia)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto vDecoder = _vStream->codec();
|
_state = PlayerState::Playing;
|
||||||
auto aDecoder = _aStream->codec();
|
_stateCond.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::decode() {
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
|
if(_state == PlayerState::Shutdown)
|
||||||
|
break;
|
||||||
|
|
||||||
|
packet = _curMedia ? _curMedia->readPacket() : Packet();
|
||||||
|
if(!packet) {
|
||||||
|
_stateCond.wait(lock, [this]{ return _state == PlayerState::Playing || _state == PlayerState::Shutdown; });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Scaler scaler(vDecoder->width(), vDecoder->height(), vDecoder->pixelFormat(),
|
|
||||||
_vSink->getWidth(), _vSink->getHeight(), _vSink->getPixelFormat());
|
|
||||||
while(auto packet = _curMedia->readPacket()) {
|
|
||||||
AVMediaType packetType = _curMedia->packetType(packet);
|
AVMediaType packetType = _curMedia->packetType(packet);
|
||||||
if(packetType == AVMEDIA_TYPE_VIDEO) {
|
if(packetType == AVMEDIA_TYPE_VIDEO) {
|
||||||
auto frame = vDecoder->decode(packet);
|
auto frame = _vStream->codec()->decode(packet);
|
||||||
frame = scaler.scale(frame);
|
frame = _scaler->scale(frame);
|
||||||
AVFrame* f = frame;
|
lock.unlock();
|
||||||
//_vSink->drawFrame(f->data, f->linesize[0]);
|
_decodedFrames.pushOrWait(frame);
|
||||||
_vSink->drawPlanarYUVFrame(f->data[0], f->data[1], f->data[2],
|
|
||||||
f->linesize[0], f->linesize[1], f->linesize[2]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::decode() {
|
void Player::displayFrames() {
|
||||||
std::cout << "decode function started" << std::endl;
|
while(true) {
|
||||||
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
|
if(_state == PlayerState::Shutdown)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(_state != PlayerState::Playing) {
|
||||||
|
_stateCond.wait(lock, [this]{ return _state == PlayerState::Playing || _state == PlayerState::Shutdown; });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
auto frame = _decodedFrames.popOrWait();
|
||||||
|
lock.lock();
|
||||||
|
AVFrame* f = frame->nativePtr();
|
||||||
|
_vSink->drawPlanarYUVFrame(f->data[0], f->data[1], f->data[2],
|
||||||
|
f->linesize[0], f->linesize[1], f->linesize[2]);
|
||||||
|
int fps = _vStream->fps();
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000/fps));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,11 +21,11 @@ namespace ffcpp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Frame Scaler::scale(Frame &inFrame) {
|
FramePtr Scaler::scale(FramePtr inFrame) {
|
||||||
Frame outFrame(_dstWidth, _dstHeight, _dstPixFmt);
|
FramePtr outFrame = std::make_shared<Frame>(_dstWidth, _dstHeight, _dstPixFmt);
|
||||||
|
|
||||||
AVFrame* fin = inFrame;
|
AVFrame* fin = inFrame->nativePtr();
|
||||||
AVFrame* fout = outFrame;
|
AVFrame* fout = outFrame->nativePtr();
|
||||||
fout->pts = fin->pts;
|
fout->pts = fin->pts;
|
||||||
|
|
||||||
int res = sws_scale(_swsContext, (uint8_t const * const *)fin->data, fin->linesize, 0, fin->height, fout->data, fout->linesize);
|
int res = sws_scale(_swsContext, (uint8_t const * const *)fin->data, fin->linesize, 0, fin->height, fout->data, fout->linesize);
|
||||||
|
|||||||
@ -30,6 +30,10 @@ namespace ffcpp {
|
|||||||
_stream->time_base = timeBase;
|
_stream->time_base = timeBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Stream::fps() const {
|
||||||
|
return _stream->avg_frame_rate.num/_stream->avg_frame_rate.den;
|
||||||
|
}
|
||||||
|
|
||||||
Stream::Stream(Stream&& stream) noexcept {
|
Stream::Stream(Stream&& stream) noexcept {
|
||||||
*this = std::move(stream);
|
*this = std::move(stream);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user