162 lines
5.1 KiB
C++
162 lines
5.1 KiB
C++
#include "ffcpp/Player.h"
|
|
#include <memory>
|
|
#include <SDL.h>
|
|
#include <SDL_thread.h>
|
|
#include <iostream>
|
|
|
|
#include <future>
|
|
|
|
namespace ff = ffcpp;
|
|
|
|
#define WINDOW_WIDTH 1280
|
|
#define WINDOW_HEIGHT 720
|
|
|
|
class SDLWindow: public ff::IVideoSink, public ff::IAudioSink {
|
|
private:
|
|
template<typename T> using SDLUniquePtr = std::unique_ptr<T, void(*)(T*)>;
|
|
using SDLWindowPtr = SDLUniquePtr<SDL_Window>;
|
|
using SDLRendererPtr = SDLUniquePtr<SDL_Renderer>;
|
|
using SDLTexturePtr = SDLUniquePtr<SDL_Texture>;
|
|
|
|
private:
|
|
SDLWindowPtr _wnd;
|
|
SDLRendererPtr _renderer;
|
|
SDLTexturePtr _texture;
|
|
SDL_AudioSpec _audioSpec;
|
|
SDL_AudioDeviceID _aDevId;
|
|
|
|
std::packaged_task<void()> _renderTask;
|
|
ff::IAudioSource* _audioSrc;
|
|
|
|
public:
|
|
SDLWindow(): _wnd(nullptr, SDL_DestroyWindow), _renderer(nullptr, SDL_DestroyRenderer), _texture(nullptr, SDL_DestroyTexture), _audioSrc(nullptr) {
|
|
int res = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
|
|
if(res < 0) throw std::runtime_error("Error initializing SDL");
|
|
|
|
_wnd.reset(SDL_CreateWindow("ffPlayer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL));
|
|
if(!_wnd) throw std::runtime_error("Error creating SDL window");
|
|
|
|
_renderer.reset(SDL_CreateRenderer(_wnd.get(), -1, 0));
|
|
if(!_renderer) throw std::runtime_error("Error creating SDL renderer");
|
|
|
|
_texture.reset(SDL_CreateTexture(_renderer.get(), SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WINDOW_WIDTH, WINDOW_HEIGHT));
|
|
if(!_texture) throw std::runtime_error("Error creating SDL texture");
|
|
|
|
SDL_AudioSpec want;
|
|
SDL_zero(want);
|
|
want.freq = 44100;
|
|
want.format = AUDIO_F32;
|
|
want.channels = 2;
|
|
want.samples = 8192;
|
|
want.callback = SDLWindow::audioCallback;
|
|
want.userdata = this;
|
|
|
|
_aDevId = SDL_OpenAudioDevice(nullptr, 0, &want, &_audioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE);
|
|
if(_aDevId == 0) throw std::runtime_error("Error opening audio device");
|
|
|
|
SDL_PauseAudioDevice(_aDevId, 0);
|
|
}
|
|
|
|
void handleEvents() {
|
|
SDL_Event event;
|
|
|
|
while(true) {
|
|
SDL_WaitEvent(&event);
|
|
switch(event.type) {
|
|
case SDL_QUIT:
|
|
return;
|
|
case SDL_USEREVENT: {
|
|
_renderTask();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
static void audioCallback(void* userdata, Uint8* stream, int len) {
|
|
ff::IAudioSource* src = static_cast<SDLWindow*>(userdata)->_audioSrc;
|
|
|
|
if(src) {
|
|
//std::cout << "fill sample buffer" << std::endl;
|
|
src->fillSampleBuffer(stream, len);
|
|
}
|
|
}
|
|
|
|
AVSampleFormat sdlToFFMpeg(SDL_AudioFormat format) {
|
|
switch (format) {
|
|
case AUDIO_S16: return AV_SAMPLE_FMT_S16;
|
|
case AUDIO_S32: return AV_SAMPLE_FMT_S32;
|
|
case AUDIO_F32: return AV_SAMPLE_FMT_FLT;
|
|
default:
|
|
throw std::runtime_error("unknown audio sample format: " + std::to_string(format));
|
|
}
|
|
}
|
|
|
|
// IVideoSink implementation
|
|
private:
|
|
virtual AVPixelFormat getPixelFormat() const noexcept override {
|
|
return AV_PIX_FMT_YUV420P;
|
|
}
|
|
|
|
virtual void drawFrame(void* pixelsData, int pitch) override {
|
|
std::cout << "drawing frame" << std::endl;
|
|
SDL_UpdateTexture(_texture.get(), nullptr, pixelsData, pitch);
|
|
SDL_RenderClear(_renderer.get());
|
|
SDL_RenderCopy(_renderer.get(), _texture.get(), nullptr, nullptr);
|
|
SDL_RenderPresent(_renderer.get());
|
|
}
|
|
|
|
virtual void drawPlanarYUVFrame(const void *yPlane, const void *uPlane, const void *vPlane, int yPitch, int uPitch, int vPitch) override {
|
|
_renderTask = std::packaged_task<void()>([=]{
|
|
SDL_UpdateYUVTexture(_texture.get(), nullptr, (const uint8_t*)yPlane, yPitch, (const uint8_t*)uPlane, uPitch, (const uint8_t*)vPlane, vPitch);
|
|
SDL_RenderClear(_renderer.get());
|
|
SDL_RenderCopy(_renderer.get(), _texture.get(), nullptr, nullptr);
|
|
SDL_RenderPresent(_renderer.get());
|
|
});
|
|
auto future = _renderTask.get_future();
|
|
|
|
SDL_Event event;
|
|
event.type = SDL_USEREVENT;
|
|
int res = SDL_PushEvent(&event);
|
|
|
|
future.get();
|
|
}
|
|
|
|
// IAudioSink implementation
|
|
private:
|
|
void setAudioSource(ff::IAudioSource* audioSrc) override {
|
|
std::cout << "set audio source" << std::endl;
|
|
_audioSrc = audioSrc;
|
|
}
|
|
|
|
AVSampleFormat getSampleFormat() override {
|
|
return sdlToFFMpeg(_audioSpec.format);
|
|
}
|
|
|
|
int getChannelsCount() override {
|
|
return _audioSpec.channels;
|
|
}
|
|
|
|
int getSampleRate() override {
|
|
return _audioSpec.freq;
|
|
}
|
|
};
|
|
|
|
int main(int argc, char** argv) {
|
|
try {
|
|
auto wnd = std::make_shared<SDLWindow>();
|
|
|
|
ff::Player player(wnd, wnd);
|
|
player.setMedia(argv[1]);
|
|
player.setVideoSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
|
player.play();
|
|
|
|
wnd->handleEvents();
|
|
} catch (...) {
|
|
std::cout << "exception" << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
} |