/*
* This file is part of Moonlight Embedded.
*
* Copyright (C) 2015 Iwan Timmer
*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see .
*/
#include "../video.h"
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif
#include
#include
#include
#include
#include
// Disables the deblocking filter at the cost of image quality
#define DISABLE_LOOP_FILTER 0x1
// Uses the low latency decode flag (disables multithreading)
#define LOW_LATENCY_DECODE 0x2
// Threads process each slice, rather than each frame
#define SLICE_THREADING 0x4
// Uses nonstandard speedup tricks
#define FAST_DECODE 0x8
// Uses bilinear filtering instead of bicubic
#define BILINEAR_FILTERING 0x10
// Uses a faster bilinear filtering with lower image quality
#define FAST_BILINEAR_FILTERING 0x20
int ffmpeg_init(int width, int height, int perf_lvl, int thread_count);
void ffmpeg_destroy(void);
int ffmpeg_draw_frame(AVFrame *pict);
AVFrame* ffmpeg_get_frame();
int ffmpeg_decode(unsigned char* indata, int inlen);
// General decoder and renderer state
static AVPacket pkt;
static AVCodec* decoder = NULL;
static AVCodecContext* decoder_ctx = NULL;
static AVFrame* dec_frame = NULL;
static AVFrame* dest_frame = NULL;
static struct SwsContext* scaler_ctx;
static int screen_width, screen_height;
static int disp_width = 0, disp_height = 0;
static char* ffmpeg_buffer = NULL;
typedef unsigned char BYTE;
GLuint id;
EGLDisplay m_display;
EGLSurface m_surface;
EGLContext m_context;
EGLConfig *m_config;
Display *XDisplay;
BYTE *image = NULL;
#define DECODER_BUFFER_SIZE 92*1024
#define BYTES_PER_PIXEL 4
// This function must be called before
// any other decoding functions
int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) {
// Initialize the avcodec library and register codecs
av_log_set_level(AV_LOG_QUIET);
avcodec_register_all();
av_init_packet(&pkt);
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
if (decoder == NULL) {
printf("Couldn't find H264 decoder");
return -1;
}
decoder_ctx = avcodec_alloc_context3(decoder);
if (decoder_ctx == NULL) {
printf("Couldn't allocate context");
return -1;
}
if (perf_lvl & DISABLE_LOOP_FILTER)
// Skip the loop filter for performance reasons
decoder_ctx->skip_loop_filter = AVDISCARD_ALL;
if (perf_lvl & LOW_LATENCY_DECODE)
// Use low delay single threaded encoding
decoder_ctx->flags |= CODEC_FLAG_LOW_DELAY;
if (perf_lvl & SLICE_THREADING)
decoder_ctx->thread_type = FF_THREAD_SLICE;
else
decoder_ctx->thread_type = FF_THREAD_FRAME;
decoder_ctx->thread_count = thread_count;
decoder_ctx->width = width;
decoder_ctx->height = height;
decoder_ctx->pix_fmt = PIX_FMT_YUV420P;
int err = avcodec_open2(decoder_ctx, decoder, NULL);
if (err < 0) {
printf("Couldn't open codec");
return err;
}
dec_frame = av_frame_alloc();
dest_frame = av_frame_alloc();
image = ( BYTE* )malloc( decoder_ctx->width * decoder_ctx->height * 4 * 2 );
avpicture_fill( ( AVPicture* )dest_frame, image, PIX_FMT_BGR32, decoder_ctx->width, decoder_ctx->height );
if (dec_frame == NULL) {
printf("Couldn't allocate frame");
return -1;
}
int filtering;
if (perf_lvl & FAST_BILINEAR_FILTERING)
filtering = SWS_FAST_BILINEAR;
else if (perf_lvl & BILINEAR_FILTERING)
filtering = SWS_BILINEAR;
else
filtering = SWS_BICUBIC;
scaler_ctx = sws_getContext(decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt, decoder_ctx->width, decoder_ctx->height, PIX_FMT_BGR32, filtering, NULL, NULL, NULL);
if (scaler_ctx == NULL) {
printf("Couldn't get scaler context");
return -1;
}
return 0;
}
// This function must be called after
// decoding is finished
void ffmpeg_destroy(void) {
if (decoder_ctx) {
avcodec_close(decoder_ctx);
av_free(decoder_ctx);
decoder_ctx = NULL;
}
if (scaler_ctx) {
sws_freeContext(scaler_ctx);
scaler_ctx = NULL;
}
if (dec_frame) {
av_frame_free(&dec_frame);
dec_frame = NULL;
}
}
int ffmpeg_draw_frame(AVFrame *pict) {
int err = sws_scale(scaler_ctx, (const uint8_t* const*) dec_frame->data, dec_frame->linesize, 0, decoder_ctx->height, pict->data, pict->linesize);
if (err != decoder_ctx->height) {
fprintf(stderr, "Scaling failed\n");
return 0;
}
return 1;
}
AVFrame* ffmpeg_get_frame() {
return dec_frame;
}
// packets must be decoded in order
// indata must be inlen + FF_INPUT_BUFFER_PADDING_SIZE in length
int ffmpeg_decode(unsigned char* indata, int inlen) {
int err;
int got_pic = 0;
pkt.data = indata;
pkt.size = inlen;
while (pkt.size > 0) {
got_pic = 0;
err = avcodec_decode_video2(decoder_ctx, dec_frame, &got_pic, &pkt);
if (err < 0) {
fprintf(stderr, "Decode failed\n");
got_pic = 0;
break;
}
pkt.size -= err;
pkt.data += err;
}
if (got_pic)
{
ffmpeg_draw_frame(dest_frame);
return 1;
}
return err < 0 ? err : 0;
}
void makeSurface()
{
// this code does the main window creation
EGLBoolean result;
Window native_window;
EGLint totalConfigsFound = 0;
// config you use OpenGL ES1.0 by default
static const EGLint context_attributes[] =
{
EGL_CONTEXT_CLIENT_VERSION, 1,
EGL_NONE
};
XSetWindowAttributes XWinAttr;
Atom XWMDeleteMessage;
Window XRoot;
XDisplay = XOpenDisplay( NULL );
if ( !XDisplay )
{
fprintf(stderr, "Error: failed to open X display.\n");
return;
}
int screen_num = DefaultScreen( XDisplay );
uint32_t w = DisplayWidth( XDisplay, screen_num );
uint32_t h = DisplayHeight( XDisplay, screen_num );
disp_width = w;
disp_height = h;
XRoot = DefaultRootWindow( XDisplay );
XWinAttr.event_mask = ExposureMask | PointerMotionMask | KeyPressMask;
native_window = XCreateWindow( XDisplay, XRoot, 0, 0, w, h, 0,
CopyFromParent, InputOutput,
CopyFromParent, CWEventMask, &XWinAttr );
XWMDeleteMessage = XInternAtom( XDisplay, "WM_DELETE_WINDOW", False );
XEvent xev;
Atom wm_state = XInternAtom( XDisplay, "_NET_WM_STATE", False );
Atom fullscreen = XInternAtom( XDisplay, "_NET_WM_STATE_FULLSCREEN", False );
memset( &xev, 0, sizeof( xev ) );
xev.type = ClientMessage;
xev.xclient.window = native_window;
xev.xclient.message_type = wm_state;
xev.xclient.format = 32;
xev.xclient.data.l[0] = 1;
xev.xclient.data.l[1] = fullscreen;
xev.xclient.data.l[2] = 0;
XMapWindow( XDisplay, native_window );
XSendEvent( XDisplay, DefaultRootWindow( XDisplay ), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev );
XFlush( XDisplay );
XStoreName( XDisplay, native_window, "RoQPlayer" );
XSetWMProtocols( XDisplay, native_window, &XWMDeleteMessage, 1 );
// get an EGL display connection
m_display = eglGetDisplay(( EGLNativeDisplayType ) XDisplay);
if( m_display == EGL_NO_DISPLAY )
{
fprintf( stderr, "EGL: error get display\n" );
exit(EXIT_FAILURE);
}
// initialize the EGL display connection
int major,minor;
result = eglInitialize( m_display, &major, &minor );
fprintf( stdout, "EGL: init version %d.%d\n", major, minor );
if( result == EGL_FALSE )
{
fprintf( stderr, "EGL: error initialising display\n" );
exit( EXIT_FAILURE );
}
// get our config from the config class
EGLConfig config = NULL;
static const EGLint attribute_list[] =
{
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
result = eglChooseConfig( m_display, attribute_list, &config, 1, &totalConfigsFound );
if ( result != EGL_TRUE || totalConfigsFound == 0 )
{
fprintf( stderr, "EGL: Unable to query for available configs, found %d.\n", totalConfigsFound );
exit( EXIT_FAILURE );
}
// bind the OpenGL API to the EGL
result = eglBindAPI( EGL_OPENGL_ES_API );
if( result == EGL_FALSE )
{
fprintf( stderr, "EGL: error binding API\n");
exit( EXIT_FAILURE );
}
// create an EGL rendering context
m_context = eglCreateContext(m_display, config, EGL_NO_CONTEXT, context_attributes);
if( m_context == EGL_NO_CONTEXT )
{
fprintf( stderr, "EGL: couldn't get a valid context\n" );
exit( EXIT_FAILURE );
}
// finally we can create a new surface using this config and window
m_surface = eglCreateWindowSurface( m_display, config, (NativeWindowType)native_window, NULL );
assert( m_surface != EGL_NO_SURFACE );
// connect the context to the surface
result = eglMakeCurrent( m_display, m_surface, m_surface, m_context );
assert( EGL_FALSE != result );
}
void initGL()
{
GLuint id;
glClearColor( 0.0, 0.0, 0.0, 0.0 );
glColor4f( 1.0, 1.0, 1.0, 1.0 );
glEnable( GL_TEXTURE_2D );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glGenTextures( 1, &id );
glBindTexture( GL_TEXTURE_2D, id );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, decoder_ctx->width, decoder_ctx->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image );
}
void resizeGL( int width, int height )
{
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrthof( 0, width, height, 0, -1, +1 );
}
void renderGL()
{
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
glActiveTexture( GL_TEXTURE0 );
glGenTextures( 1, &id );
glBindTexture( GL_TEXTURE_2D, id );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, decoder_ctx->width, decoder_ctx->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image );
float vtxcoords[] =
{
0, 0,
disp_width, 0,
0, disp_height,
disp_width, disp_height,
};
float texcoords[] = { 0, 0, 1, 0, 0, 1, 1, 1 };
glVertexPointer( 2, GL_FLOAT, 0, vtxcoords );
glTexCoordPointer( 2, GL_FLOAT, 0, texcoords );
glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
eglSwapBuffers( m_display, m_surface );
glBindTexture( GL_TEXTURE_2D, 0 );
glDisableClientState( GL_TEXTURE_COORD_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );
}
void decoder_renderer_setup(int width, int height, int redrawRate, void* context, int drFlags)
{
int avc_flags = FAST_BILINEAR_FILTERING;
if (ffmpeg_init(width, height, 2, avc_flags) < 0)
{
fprintf(stderr, "Couldn't initialize video decoding\n");
exit(1);
}
ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE);
if (ffmpeg_buffer == NULL)
{
fprintf(stderr, "Not enough memory\n");
exit(1);
}
makeSurface();
screen_width = width;
screen_height = height;
initGL();
resizeGL( disp_width, disp_height );
}
void decoder_renderer_cleanup()
{
ffmpeg_destroy();
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if(eglDestroySurface(m_display, m_surface) && eglDestroyContext(m_display, m_context) && eglTerminate(m_display))
{
m_display = m_surface = m_context = NULL;
fprintf( stdout, "EGL: destroy context ok\n" );
}
}
int decoder_renderer_submit_decode_unit(PDECODE_UNIT decodeUnit)
{
if (decodeUnit->fullLength < DECODER_BUFFER_SIZE)
{
PLENTRY entry = decodeUnit->bufferList;
int length = 0;
while (entry != NULL)
{
memcpy(ffmpeg_buffer+length, entry->data, entry->length);
length += entry->length;
entry = entry->next;
}
int ret = ffmpeg_decode(ffmpeg_buffer, length);
if (ret == 1)
{
//AVFrame* frame = ffmpeg_get_frame();
renderGL();
}
}
else
{
fprintf(stderr, "Video decode buffer too small");
exit(1);
}
return DR_OK;
}
DECODER_RENDERER_CALLBACKS decoder_callbacks_fake = {
.setup = decoder_renderer_setup,
.cleanup = decoder_renderer_cleanup,
.submitDecodeUnit = decoder_renderer_submit_decode_unit,
};