/* Sample application to run multiple videos. Files to be played are hardcorded in the code with the variable "FileName". */
/* Application supports mov/mpegts container formats, video codec : mpeg2 or h264. Audio not supported in this application.*/

/* To compile the application do: */
/* gcc -I/usr/include/gstreamer-0.10 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/libxml2 -I/usr/lib/liboil-0.3 -I/usr/include -L/usr/lib/gstreamer-0.10 -lglib-2.0 -lgmodule-2.0 -lgstreamer-0.10 -lxml2 -lgobject-2.0 -lgthread-2.0  -lgstbase-0.10 -lX11 -lgstinterfaces-0.10 -lgstvaapi-x11-0.10 -lgstvaapi-0.10 MultiVideo_post.c -o MultiVideo */
/**************************************************************************************************************************************************/

/*------------------------------ System Includes -----------------------------*/
#include <string.h>
#include <gst/gst.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <pthread.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <gst/interfaces/xoverlay.h>


/*------------------------------ Macros -----------------------------*/
#define MAX_VIDEOS     4   /* Maximum number of videos that can be played.*/
#define NOOFWINDOWS    4   /* Max No of windows that can be created simultaneously. Range is 1-4 */
#define MPEGTS         0   /* To playback mpegts files */
#define MOV            1   /* To playback mov/mp4 files */
                           /* Right now files are hardcoded for mov filescan be changed to ts files */

#define ENABLE_DBGPRINT 0  /* Macro to enable printf*/
/* Macro for printf */
#if (1 == ENABLE_DBGPRINT)
#define PRINT(fmt, args...) \
        printf("\n DEBUG : TEST => %s : %s:\n " fmt "\n", __FILE__, __FUNCTION__ , ## args); 
#else
#define PRINT(fmt, args...)
#endif/*!< #if(1 == ENABLE_DBGPRINT) */



/*------------------------------ Global variables -----------------------------*/
/*Instance of the displayplay connection*/
Display *display;

/*Files to be played*/
char FileName[MAX_VIDEOS][256] = {"City Video Clips _1280.mov", 
                                  "City Video Clips _1280.mov",
                                  "City Video Clips _1280.mov",
                                  "City Video Clips _1280.mov"};

/*Variables for setting the window location, width and height */
int x[MAX_VIDEOS] = {0, 550, 0, 550};
int y[MAX_VIDEOS] = {0, 0, 550 , 550};
int width[MAX_VIDEOS] = {500, 500, 500, 500};
int height[MAX_VIDEOS] = {500, 500, 500, 500};

/*------------------------------ Structure Declaration -----------------------------*/
/*Holds general info like window ID, width , height etc.*/
typedef struct _tag_APP_GENERALINFO_ST
{
    Window win;
    int x;
    int y;
    int width;
    int height;
    char currentfilename[256];
}APP_GENERALINFO_ST;

/* Stores information specific to gstreamer */
typedef struct _tag_APP_GSTINFO_ST
{
    GstElement *pipeline;
    GstElement *filesrc;
    GstElement *demux;
    GstElement *videodecoder;
    GstElement *videobin;
    GstElement *videosink;
    GstPad *videofilterpad;
    GstElement *videoqueue;
    GstBus *bus;    
    GMainLoop *loop;
}APP_GSTINFO_ST;

/*Stores all the infomation required by the application*/
typedef struct _tag_APP_INFO_ST
{
    APP_GSTINFO_ST stAppGstInfo; 
    APP_GENERALINFO_ST stAppGeneralInfo;
}APP_INFO_ST;

/*------------------------------ Function Definitions------------------------*/
/******************************************************************************
* Function               :   InitializeAppInfostruct
* Description            :   Api to initialize APP_INFO_ST structure.
*                            This will also set the width, height, filename etc.
* *****************************************************************************/
void InitializeAppInfostruct(APP_INFO_ST *pstAppInfo, int cntNoofWindows)
{
  memset(pstAppInfo, 0, sizeof(APP_INFO_ST));  

  pstAppInfo->stAppGeneralInfo.x = x[cntNoofWindows];
  pstAppInfo->stAppGeneralInfo.y = y[cntNoofWindows];
  pstAppInfo->stAppGeneralInfo.width = width[cntNoofWindows];
  pstAppInfo->stAppGeneralInfo.height = height[cntNoofWindows];
  strcpy(pstAppInfo->stAppGeneralInfo.currentfilename,
           FileName[cntNoofWindows]);

  return;
}

/******************************************************************************
* Function               :   CreateApp_Window
* Description            :   Api to create window and to give the window ID 
*                            to vaapi sink. 
*                            Window ID to be created by the application only 
*                            when 'prepare-xwindow-id' message comes.
******************************************************************************/
static GstBusSyncReply
CreateApp_Window(GstBus * bus, GstMessage * message, void *pvAppGeneralInfo)
{
  APP_GENERALINFO_ST *pstAppGeneralInfo = pvAppGeneralInfo;

  PRINT("Message src = %s\n", gst_object_get_name(GST_MESSAGE_SRC (message)));
  /* Ignore all other messages except 'prepare-xwindow-id' element messages */

  if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
    return GST_BUS_PASS;

  if (!gst_structure_has_name (message->structure, "prepare-xwindow-id"))
    return GST_BUS_PASS;

  /* This is to displayable the window manager decorations i.e border, title bar etc.*/
  XSetWindowAttributes stXSWAttr = {0};
  int i32XWindowMask = 0;

  /* Background color set to black for the displayplay.*/
  stXSWAttr.background_pixel = BlackPixel(display, 0);

  /* Set flag to tell Window manager to not intercept commands to this 
   * window. */
  stXSWAttr.override_redirect = True;

  /* Set mask. */
  i32XWindowMask = CWBackPixel|CWBorderPixel|CWOverrideRedirect;

  /* Create X window */
  pstAppGeneralInfo->win = XCreateWindow(display,
                                         RootWindow(display,0),
                                         pstAppGeneralInfo->x,
                                         pstAppGeneralInfo->y,
                                         pstAppGeneralInfo->width,
                                         pstAppGeneralInfo->height,
                                         0, 
                                         DefaultDepth(display,0),
                                         InputOutput,
                                         CopyFromParent,
                                         i32XWindowMask,
                                         &stXSWAttr);
   
  /*Map the contents of the window to displayplay */
  XMapWindow(display, pstAppGeneralInfo->win);

  XFlush (display); 
  XSync (display, False);

  /* Tell the sink to render on the X window ID created above */
  gst_x_overlay_set_xwindow_id(GST_X_OVERLAY (GST_MESSAGE_SRC (message)), 
                              pstAppGeneralInfo->win);

  gst_message_unref (message);

  return GST_BUS_DROP;
}

/******************************************************************************
* Function               :   bus_callback
* Description            :   Callback api for bus to receives messages
*                            like EOS, error etc. 
******************************************************************************/
static gboolean bus_callback (GstBus     *bus,
                              GstMessage *message,
                              void    *pvAppInfo)
{
  APP_INFO_ST *pstAppInfo = pvAppInfo;

  PRINT("Message src = %s", gst_object_get_name(GST_MESSAGE_SRC (message)));

  switch (GST_MESSAGE_TYPE (message)) 
  {
    case GST_MESSAGE_ERROR: 
    {
      GError *err;
      gchar *debug;

      gst_message_parse_error (message, &err, &debug);
      g_print ("Error: %s\n", err->message);
      g_error_free (err);
      g_free (debug);

      g_main_loop_quit (pstAppInfo->stAppGstInfo.loop);
      break;
    }/* End of case GST_MESSAGE_ERROR*/
        
    case GST_MESSAGE_EOS:
    {
      /* end-of-stream received */
      PRINT("Received EOS");

      /* Stop the gmain loop */
      g_main_loop_quit (pstAppInfo->stAppGstInfo.loop);
            
      /* Destroy the window */
      XDestroyWindow(display, pstAppInfo->stAppGeneralInfo.win);

      /* Set the pipeline to NULL state */       
      gst_element_set_state (pstAppInfo->stAppGstInfo.pipeline, 
                                   GST_STATE_NULL);

      /* Set the pipeline to PLAYING state */       
      gst_element_set_state (pstAppInfo->stAppGstInfo.pipeline, 
                                   GST_STATE_PLAYING);

      /* Create gthread again and run gmain loop */       
      g_thread_create ((GThreadFunc)g_main_loop_run, 
                             pstAppInfo->stAppGstInfo.loop,  
                             TRUE, 
                             NULL);

      break;
    }/* End of case GST_MESSAGE_EOS*/

    default:
    break;

  }/* End of switch*/

  return TRUE;
}

/******************************************************************************
* Function               :   cb_newpad
* Description            :   Linking the demuxer pad to video bin pad.
******************************************************************************/
static void
cb_newpad (GstElement *demuxbin,
       GstPad     *pad,
       gpointer    data)
{
  GstCaps *caps;
  GstStructure *str;
  GstPad *videopad;
  gchar *tex;
  GstElement *trybin = (GstElement *)data;

  /* check media type */
  caps = gst_pad_get_caps (pad);
  str = gst_caps_get_structure (caps, 0);
  tex = (gchar*)gst_structure_get_name(str);

  if(g_strrstr(tex,"audio"))
  {
     return;
  }

  if(g_strrstr(tex,"video"))
  {
    videopad = gst_element_get_static_pad(trybin,"sink");
    if(GST_PAD_IS_LINKED(videopad))
    {
      g_object_unref(videopad);
    }
    else
    {
      gst_pad_link(pad,videopad);
      g_object_unref (videopad);
    }
      return;
  }
}

/******************************************************************************
* Function               :   main
* Description            :   main of the application.
* ******************************************************************************/
int main (int argc, char *argv[])
{
  /* Variable Declaration */
  gboolean ret = 0;
  GstStateChangeReturn State = GST_STATE_NULL;     
  int cntNoOfWindows = 0;
  APP_INFO_ST stAppInfo[NOOFWINDOWS];

  /* initialize GStreamer */
  gst_init (NULL, NULL);

  display = XOpenDisplay(NULL);

  while(cntNoOfWindows < NOOFWINDOWS)
  {      
    PRINT("APPLICATION Started -----------------------");
   
    /* Initialize AppInfo struct */
    InitializeAppInfostruct(&stAppInfo[cntNoOfWindows], cntNoOfWindows);

    /* Create all elements for the following pipeline */
    /* filesrc ! demux ! queue ! decoder ! sink*/

    stAppInfo[cntNoOfWindows].stAppGstInfo.pipeline = 
    gst_pipeline_new ("Pipeline to play video using vaapi");
    if(NULL == stAppInfo[cntNoOfWindows].stAppGstInfo.pipeline)
    {
      PRINT("ERROR : pipeline could not be created : aborting application");
      return -1;
    }
    
    /* Create a Gmain LOOP */
    stAppInfo[cntNoOfWindows].stAppGstInfo.loop = g_main_loop_new (NULL, FALSE);

    /* Create a Bus */
    stAppInfo[cntNoOfWindows].stAppGstInfo.bus = 
    gst_pipeline_get_bus (GST_PIPELINE (stAppInfo[cntNoOfWindows].\
                          stAppGstInfo.pipeline));

    /* Attach a call-back to the BUS */
    gst_bus_add_watch (stAppInfo[cntNoOfWindows].stAppGstInfo.bus, 
                       bus_callback, 
                       &stAppInfo[cntNoOfWindows]);

    /* Set sync handler to create foriegn X window */
    gst_bus_set_sync_handler (stAppInfo[cntNoOfWindows].\
                              stAppGstInfo.bus, 
                             (GstBusSyncHandler) CreateApp_Window, 
                              &(stAppInfo[cntNoOfWindows].stAppGeneralInfo));

    gst_object_unref (stAppInfo[cntNoOfWindows].stAppGstInfo.bus);    

    stAppInfo[cntNoOfWindows].stAppGstInfo.filesrc = 
    gst_element_factory_make ("filesrc", "file source");
    if(NULL == stAppInfo[cntNoOfWindows].stAppGstInfo.filesrc)
    {
      PRINT("ERROR : Filesrc could not be created : aborting application");
      return -1;
    }

#if MPEGTS
    demux = gst_element_factory_make ("ffdemux_mpegts", "demux");
#endif
#if MOV
    stAppInfo[cntNoOfWindows].stAppGstInfo.demux = 
    gst_element_factory_make ("ffdemux_mov_mp4_m4a_3gp_3g2_mj2", "demux");
#endif
    if(NULL == stAppInfo[cntNoOfWindows].stAppGstInfo.demux)
    {
      PRINT("ERROR : Demuxer could not be created : aborting application");
      return -1;
    }

    stAppInfo[cntNoOfWindows].stAppGstInfo.videodecoder = 
    gst_element_factory_make ("vaapidecode", "decoder");
    if(NULL == stAppInfo[cntNoOfWindows].stAppGstInfo.videodecoder)
    {
      PRINT("ERROR : Vaapi Decoder could not be created : aborting application");
      return -1;
    }

    stAppInfo[cntNoOfWindows].stAppGstInfo.videosink = 
    gst_element_factory_make ("vaapisink", "sink");
    if(NULL == stAppInfo[cntNoOfWindows].stAppGstInfo.videosink)
    {
      PRINT("ERROR : Vaapi sink could not be created : aborting application");
      return -1;
    }    

    stAppInfo[cntNoOfWindows].stAppGstInfo.videoqueue = 
    gst_element_factory_make ("queue", "videoqueue");
    if(NULL == stAppInfo[cntNoOfWindows].stAppGstInfo.videoqueue)
    {
      PRINT("ERROR : Video Queue could not be created : aborting application");
      return -1;
    }

    g_object_set (G_OBJECT (stAppInfo[cntNoOfWindows].stAppGstInfo.filesrc), 
                  "location", 
                  (unsigned char*)stAppInfo[cntNoOfWindows].stAppGeneralInfo.\
                  currentfilename, 
                  NULL);

    gst_bin_add_many (GST_BIN (stAppInfo[cntNoOfWindows].stAppGstInfo.pipeline), 
                      stAppInfo[cntNoOfWindows].stAppGstInfo.filesrc, 
                      stAppInfo[cntNoOfWindows].stAppGstInfo.demux,NULL);

    gst_element_link_many (stAppInfo[cntNoOfWindows].stAppGstInfo.filesrc, 
                           stAppInfo[cntNoOfWindows].stAppGstInfo.demux, 
                           NULL);

    /* create video bin */
    stAppInfo[cntNoOfWindows].stAppGstInfo.videobin = gst_bin_new ("videobin");

    gst_bin_add_many (GST_BIN (stAppInfo[cntNoOfWindows].stAppGstInfo.videobin), 
                      stAppInfo[cntNoOfWindows].stAppGstInfo.videoqueue,
                      stAppInfo[cntNoOfWindows].stAppGstInfo.videodecoder,
                      stAppInfo[cntNoOfWindows].stAppGstInfo.videosink, NULL);

    gst_bin_add(GST_BIN(stAppInfo[cntNoOfWindows].stAppGstInfo.pipeline), 
                        stAppInfo[cntNoOfWindows].stAppGstInfo.videobin);

    stAppInfo[cntNoOfWindows].stAppGstInfo.videofilterpad = 
    gst_element_get_static_pad (stAppInfo[cntNoOfWindows].stAppGstInfo.videoqueue, 
                                "sink");

    gst_element_add_pad (stAppInfo[cntNoOfWindows].stAppGstInfo.videobin, 
    gst_ghost_pad_new ("sink", 
                       stAppInfo[cntNoOfWindows].stAppGstInfo.videofilterpad));

    g_signal_connect (stAppInfo[cntNoOfWindows].stAppGstInfo.demux, 
                      "pad-added", 
                      G_CALLBACK (cb_newpad), 
                      stAppInfo[cntNoOfWindows].stAppGstInfo.videobin);

    ret = gst_element_link_many (stAppInfo[cntNoOfWindows].stAppGstInfo.videoqueue,
                                 stAppInfo[cntNoOfWindows].stAppGstInfo.videodecoder, 
                                 stAppInfo[cntNoOfWindows].stAppGstInfo.videosink, 
                                 NULL);

    if(0 == ret)
    {
      PRINT("Failed to link queue, decoder and sink");
      return -1;
    }

    gst_object_unref(stAppInfo[cntNoOfWindows].stAppGstInfo.videofilterpad);

    /* Change the pipeline state to READY */
    State = 
    gst_element_set_state (stAppInfo[cntNoOfWindows].stAppGstInfo.pipeline,
                           GST_STATE_READY);
    if(GST_STATE_CHANGE_FAILURE == State) 
    {
      PRINT("State = %d", State);
      return 0;
    }
    
    /* Change the pipeline state to PLAYING */
    State = 
    gst_element_set_state (stAppInfo[cntNoOfWindows].stAppGstInfo.pipeline, 
                           GST_STATE_PLAYING);
    if(GST_STATE_CHANGE_FAILURE == State) 
    {
      PRINT("State = %d", State);
      return 0;
    }
    
    /*Start the gmain loop by creating a gthread*/
    g_thread_create ((GThreadFunc)g_main_loop_run, 
                     stAppInfo[cntNoOfWindows].stAppGstInfo.loop,  
                     FALSE, 
                     NULL);          
    sleep(1);   
    
    cntNoOfWindows++;
  }
      
  while(1);

  return 0;

}

/* End of application */

