/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 06/25/2014
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/
#ifdef WIN32
#define __USE_MINGW_ANSI_STDIO 1
#endif

#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#ifndef PRId64
    #error PRId64 is NOT defined
#endif

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/time.h>

#include <string>
#include <vector>

#ifndef WIN32
#include <pthread.h>
#endif

extern "C" {
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
}

#include "vn_capture.h"
#include "MediaClientImpl.h"
#include "impl/Mutex.h"

#ifdef WIN32
#include "vn_capture_dshow.h"
#endif

#define MAX_PREVIEW_HEIGHT 480

using namespace videonext::media;

enum {
    NAL_SLICE=1,
    NAL_DPA,
    NAL_DPB,
    NAL_DPC,
    NAL_IDR_SLICE,
    NAL_SEI,
    NAL_SPS,
    NAL_PPS,
    NAL_AUD,
    NAL_END_SEQUENCE,
    NAL_END_STREAM,
    NAL_FILLER_DATA,
    NAL_SPS_EXT,
    NAL_AUXILIARY_SLICE=19
};

struct vn_capture_context_t
{
    //  A/V capturing
    AVFormatContext *s;
    char **audio_devices;
    char **video_devices;
    char **video_resolutions;
    AVInputFormat *iformat;

    struct SwsContext *sws_ctx;
    AVFrame *yuv420_pic;

    // preview
    struct SwsContext *rgb24_sws_ctx;
    AVFrame *rgb24_pic;
    
    // video encoding
    struct AVCodecContext *video_enc_ctx;
    int video_width;
    int video_height;
    AVFrame *frame;
    int video_bit_rate;
    int video_frame_rate;
    uint8_t *video_enc_buf;
    unsigned video_enc_buf_size;
    bool first_frame;
    
    PushMediaClient *push_client;
    bool stop_flag;

    struct vn_capture_callbacks_t callbacks;
    /*
     * Provided by user opaque pointer
     */
    void *callbacks_client_data;   

    videonext::media::Mutex *callbacks_mutex;
    
#ifdef WIN32
    HANDLE thr;
#else
    pthread_t thr;
#endif

};

static void split(const std::string& str, const std::string& delimiters, std::vector<std::string> &tokens)
{
    std::string::size_type last_pos = str.find_first_not_of(delimiters, 0);
    std::string::size_type pos = str.find_first_of(delimiters, last_pos);
    
    while (std::string::npos != pos || std::string::npos != last_pos) {
        tokens.push_back(str.substr(last_pos, pos - last_pos));
        last_pos = str.find_first_not_of(delimiters, pos);
        pos = str.find_first_of(delimiters, last_pos);
    }
}

static int init_video_encoder(vn_capture_context_t *ctx)
{
    ctx->first_frame = true;
    ctx->frame = av_frame_alloc();
    ctx->frame->pts = 0;
    
    // init encoder
    const struct AVCodec *video_enc_codec;
    video_enc_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!video_enc_codec) 
    {
        fprintf(stderr, "H264 codec not found");
        return -1;
    }

    ctx->video_enc_ctx = avcodec_alloc_context3(video_enc_codec);

    // tune encoder
    ctx->video_enc_ctx->bit_rate = ctx->video_bit_rate;
    ctx->video_enc_ctx->bit_rate_tolerance = ctx->video_bit_rate * 1.5;
    
    ctx->video_enc_ctx->time_base    =  (AVRational){1000000/ctx->video_frame_rate, 1000000};

    ctx->video_enc_ctx->gop_size     = ctx->video_frame_rate; /* emit one intra frame every second */
    ctx->video_enc_ctx->max_b_frames = 0;

    ctx->video_enc_ctx->width        = ctx->video_width;  
    ctx->video_enc_ctx->height       = ctx->video_height;
    ctx->video_enc_ctx->pix_fmt      = AV_PIX_FMT_YUV420P;
    ctx->video_enc_ctx->flags       |= AV_CODEC_FLAG_GLOBAL_HEADER;

    if (av_opt_set(ctx->video_enc_ctx->priv_data, "profile", "baseline", 0) != 0)
    {
        fprintf(stderr, "Failed to set H264 profile to 'baseline'\n");
    }
        
    /* Disable intra-refresh */
    if (av_opt_set_int(ctx->video_enc_ctx->priv_data, "intra-refresh", 0, 0) != 0)
    {
        fprintf(stderr, "Failed to set x264 intra-refresh\n");
    }
        
    // /* Misc x264 settings (performance, quality, latency, etc).
    //  * Let's just use the x264 predefined preset & tune.
    //  */
    if (av_opt_set(ctx->video_enc_ctx->priv_data, "preset", "faster", 0) != 0) 
    {
        fprintf(stderr, "Failed to set x264 preset 'fast'\n");
    }
        
    if (av_opt_set(ctx->video_enc_ctx->priv_data, "tune", "animation+zerolatency", 0) != 0) 
    {
        fprintf(stderr, "Failed to set x264 tune 'zerolatency'\n");
    }
    
    if (avcodec_open2(ctx->video_enc_ctx, video_enc_codec, 0) < 0) 
    {
        fprintf(stderr, "Could't open H264 encoder\n");
        return -1;
    }
	       
    ctx->video_enc_buf = (uint8_t*)malloc(ctx->video_enc_buf_size);
    if (!ctx->video_enc_buf) 
    {
        fprintf(stderr, "malloc() failed\n");
        return -1;
    }

    return 0;
}

vn_capture_context_t *vn_capture_create()
{
    avdevice_register_all();

    vn_capture_context_t *ctx = (vn_capture_context_t *)calloc(1, sizeof(vn_capture_context_t));

    do
    {    
        ctx->iformat = (AVInputFormat *)av_find_input_format("dshow");
        if (!ctx->iformat)            
            break;

        ctx->callbacks_mutex = new videonext::media::Mutex;



        

        return ctx;
        
    } while (0);

    
    free(ctx);
    return NULL;    
}

void vn_capture_set_callbacks(vn_capture_context_t *ctx, const vn_capture_callbacks_t *callbacks, void *client_data)
{
    videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
    if (callbacks == 0)
    {
        bzero(&ctx->callbacks, sizeof(vn_capture_callbacks_t));
    }
    else
    {
        ctx->callbacks             = *callbacks;
        ctx->callbacks_client_data = client_data;
    }
}

#ifdef WIN32
static DWORD WINAPI vn_capture_thread(LPVOID arg)
#else
static void* vn_capture_thread(void* arg)
#endif
{
   vn_capture_context_t *ctx = (vn_capture_context_t *)arg;

   int video_stream = 1;
   for (unsigned i = 0; i < ctx->s->nb_streams; i++)
   {
       if (ctx->s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
       {
           video_stream = i;
           break;
       }
   }

   fprintf(stderr, "start time: %" PRId64"\n", ctx->s->start_time);
   fprintf(stderr, "time base for video: %d/%d\n", ctx->s->streams[1]->time_base.num, ctx->s->streams[1]->time_base.den);
   fprintf(stderr, "time base for audio: %d/%d\n", ctx->s->streams[0]->time_base.num, ctx->s->streams[0]->time_base.den);

   
   int res;
   uint64_t audio_stat_last = 0;
   while (!ctx->stop_flag)
   {
       AVPacket pkt;

       if (ctx->stop_flag)
           break;

       av_init_packet(&pkt);

       res = av_read_frame(ctx->s, &pkt);
       if (res < 0)
       {
           videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
           if (ctx->callbacks.on_error)
           {
               mg.release();
               ctx->callbacks.on_error(res, "Failed to read next frame", ctx->callbacks_client_data);
               mg.acquire();
           }
           break;
       }

       int64_t dts_us = (pkt.dts - ctx->s->start_time*10)/10;
       
       struct timeval now;
       now.tv_sec  = dts_us/1000000;
       now.tv_usec = dts_us%1000000;
       
       char now_str[256] = {0};
       sprintf(now_str, "%ld.%d", now.tv_sec, (int)now.tv_usec);

//       fprintf(stderr, "%lld\n", dts_us);
//       fprintf(stderr, "Timestamp: %s\n", now_str);
       
       if (pkt.stream_index == video_stream)
       {
           AVFrame *pic_to_encode = ctx->frame;
           
           if (ctx->sws_ctx)
           {
               pic_to_encode = ctx->yuv420_pic;

               av_image_fill_arrays(ctx->frame->data, ctx->frame->linesize, pkt.data, AV_PIX_FMT_YUYV422, ctx->video_width, ctx->video_height, 0);

               sws_scale(ctx->sws_ctx, ctx->frame->data, ctx->frame->linesize, 
                         0, ctx->video_height, ctx->yuv420_pic->data, ctx->yuv420_pic->linesize);
           }
           else
           {
               av_image_fill_arrays(ctx->frame->data, ctx->frame->linesize, pkt.data, AV_PIX_FMT_YUV420P, ctx->video_width, ctx->video_height, 0);
           }

           // preview
           if (ctx->rgb24_sws_ctx)
           {
               if (ctx->stop_flag)
                   break;

               sws_scale(ctx->rgb24_sws_ctx, pic_to_encode->data, pic_to_encode->linesize, 
                         0, ctx->video_height, ctx->rgb24_pic->data, ctx->rgb24_pic->linesize);

               videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
               if (ctx->callbacks.on_new_preview_image)
               {
                   mg.release();
                   ctx->callbacks.on_new_preview_image(ctx->callbacks_client_data);
                   mg.acquire();
               }               
           }

           
           ctx->frame->pts =
               av_rescale_q(dts_us, (AVRational){1, 1000000}, ctx->video_enc_ctx->time_base);

           pic_to_encode->width = ctx->video_width;
           pic_to_encode->height = ctx->video_height;
           pic_to_encode->format = AV_PIX_FMT_YUV420P;

           
//           fprintf(stderr, "pts: %lld\n", ctx->frame->pts);
           
           unsigned char *out_buf_ptr = ctx->video_enc_buf + ctx->video_enc_ctx->extradata_size ;

           AVPacket pkt;
           av_init_packet(&pkt);
           pkt.data = out_buf_ptr;
           pkt.size = ctx->video_enc_buf_size - ctx->video_enc_ctx->extradata_size;

           int got_packet = 0;
           int ret = avcodec_encode_video2(ctx->video_enc_ctx, &pkt,
                                           pic_to_encode, &got_packet);

           int size = pkt.size;

           if (ret != 0 || !got_packet)
           {
               fprintf(stderr, "Could't encode frame\n");
               av_free_packet(&pkt);
               continue;
           }

           if (ctx->first_frame)
           {
               // strip SEI NALU if exists
               if (out_buf_ptr[0] == 0x00 && out_buf_ptr[1] == 0x00 
                   && out_buf_ptr[2] == 0x01 && out_buf_ptr[3] == NAL_SEI)
               {
                   // Looking for NAL_IDR_SLICE
//                   fprintf(stderr, "SEI found. Looking for IDR SLICE\n");

                   unsigned char *idr_slice = 0;
                   int n = 0;
                   do
                   {        
                       if (out_buf_ptr[n] == 0x00 && out_buf_ptr[n+1] == 0x00 && out_buf_ptr[n+2] == 0x00 
                           && out_buf_ptr[n+3] == 0x01  && (out_buf_ptr[n+4]&0x1F) == NAL_IDR_SLICE)
                       {
                           idr_slice = &out_buf_ptr[n];
                           break;
                       }
                   }
                   while (n++ < size - 4);
            
                   if (idr_slice)
                   {
//                       fprintf(stderr, "IDR SLICE found. Size was: %d, new: %d\n", size, size - (idr_slice - out_buf_ptr));
                       size = size - (idr_slice - out_buf_ptr);
                       memmove(out_buf_ptr, idr_slice, size);
                   }
               }
               ctx->first_frame = false;
           }
	
           if (out_buf_ptr[0] == 0x00 && out_buf_ptr[1] == 0x00 && out_buf_ptr[2] == 0x00 
               && out_buf_ptr[3] == 0x01  && (out_buf_ptr[4]&0x1F) == NAL_IDR_SLICE)
           {
//               fprintf(stderr, "IDR\n");
               // Copying SPS/PPS to the front of frame
               memcpy(ctx->video_enc_buf, ctx->video_enc_ctx->extradata, ctx->video_enc_ctx->extradata_size);
               size += ctx->video_enc_ctx->extradata_size;
               out_buf_ptr = ctx->video_enc_buf;
           }
		
           // if (time(0) > video_bandwidth_time)
           // {
           //     fprintf(stderr, "%ld: %d bytes (%dfps)\n", video_bandwidth_time, video_bandwidth, video_fps);
           //     video_bandwidth = 0;
           //     video_fps = 0;
           //     video_bandwidth_time = time(0);
           // }

           // video_bandwidth += size;
           // video_fps++;


           
           // static FILE *f = fopen("tmp/video.h264", "wb");
           // fwrite(out_buf_ptr, size, 1, f);
           // fflush(f);

           
           //fprintf(stderr, "%ld V:  %lld/%lld, size: %d\n", time(0), pkt.dts, pkt.pts, size);
           //fprintf(stdout, "V: %lld/%lld\n", (pkt.dts - ctx->s->start_time*10)/10, (pkt.pts - ctx->s->start_time*10)/10);

           if (ctx->stop_flag)
               break;
           // send frame
           videonext::media::DynaParams headers;	
           headers.insert(std::make_pair("Content-Type", "video/h264"));
           headers.insert(std::make_pair("Timestamp", now_str));
           
           ResultStatus result;
           ctx->push_client->postFrame(&result, out_buf_ptr, size, &headers);

           if (result.errorCode)
           {
               fprintf(stderr, "Post error: %s\n", result.errorString.c_str());

               videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
               if (ctx->callbacks.on_error)
               {
                   char error[256];
                   snprintf(error, sizeof(error), "Post error: %s", result.errorString.c_str());
                   mg.release();
                   ctx->callbacks.on_error(result.errorCode, error, ctx->callbacks_client_data);
                   mg.acquire();
               }

               av_free_packet(&pkt);

               break;
           }
       }
       else // audio
       {
           if (ctx->stop_flag)
               break;

           //fprintf(stdout, "A: %lld/%lld\n", (pkt.dts - ctx->s->start_time*10)/10, (pkt.pts - ctx->s->start_time*10)/10);

           // send frame
           videonext::media::DynaParams headers;	
           headers.insert(std::make_pair("Content-Type", "audio/L16/16000/2"));
           headers.insert(std::make_pair("Timestamp", now_str));
           
           ResultStatus result;
           ctx->push_client->postFrame(&result, pkt.data, pkt.size, &headers);

           if (result.errorCode)
           {
               fprintf(stderr, "Post error: %s\n", result.errorString.c_str());

               videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
               if (ctx->callbacks.on_error)
               {
                   char error[256];
                   snprintf(error, sizeof(error), "Post error: %s", result.errorString.c_str());
                   mg.release();
                   ctx->callbacks.on_error(result.errorCode, error, ctx->callbacks_client_data);
                   mg.acquire();
               }
               
               av_free_packet(&pkt);

               break;
           }

           videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
           if (ctx->callbacks.on_audio_stats)
           {
               struct timeval tv;
               gettimeofday(&tv, 0);
               uint64_t t = (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
               
               // send audio stats (no more than 10 times per sec);
               if (t - audio_stat_last > 100000)
               {
                   audio_stat_last = t;

                   short samples[32];
                   for (int i = 0; i < 64; i+=2)
                   {                   
                       short s  = ((short)pkt.data[i]) | ((short)pkt.data[i+1] << 8);
                       samples[i/2] = s;
                   }
                   
                   mg.release();
                   ctx->callbacks.on_audio_stats(samples, 32, ctx->callbacks_client_data);
                   mg.acquire();
               }
           }
       }

       av_free_packet(&pkt);
   }

   return 0;
}

char **vn_capture_get_audio_devices(vn_capture_context_t *ctx)
{
    if (ctx->audio_devices)
    {
        char **devices = ctx->audio_devices;
        for (char **audio_device = devices; *audio_device != NULL; audio_device++)
        {
            free(*audio_device);
        }
        free(ctx->audio_devices);

        ctx->audio_devices = 0;
    }
    
#ifdef WIN32
        ctx->audio_devices = vn_capture_get_device_list(1);
#endif

    return ctx->audio_devices;
}

char **vn_capture_get_video_devices(vn_capture_context_t *ctx)
{
    if (ctx->video_devices)
    {
        char **devices = ctx->video_devices;
        for (char **video_device = devices; *video_device != NULL; video_device++)
        {
            free(*video_device);
        }
        free(ctx->video_devices);
        
        ctx->video_devices = 0;
    }

#ifdef WIN32
    ctx->video_devices = vn_capture_get_device_list(0);
#endif

    return ctx->video_devices;
}

char **vn_capture_get_video_device_parameters(vn_capture_context_t *ctx, const char *device_name)
{
#ifdef WIN32    
    if (ctx->video_resolutions)
    {
        vn_capture_free_resolutions_list(ctx->video_resolutions); ctx->video_resolutions = 0;
    }
    ctx->video_resolutions = vn_capture_get_resolutions_list(device_name);
#endif
    return ctx->video_resolutions;
}

static void vn_capture_cleanup(vn_capture_context_t *ctx)
{
    if (ctx->s)
        avformat_close_input(&ctx->s);

    delete ctx->push_client;
    ctx->push_client = 0;

    if (ctx->video_enc_ctx) 
    {
        avcodec_close(ctx->video_enc_ctx);
        av_free(ctx->video_enc_ctx);
    }
      
    if (ctx->frame)
    {
        av_frame_free(&ctx->frame); 
    }
      
    if (ctx->video_enc_buf)
        free(ctx->video_enc_buf);

    if (ctx->yuv420_pic)
    {
        avpicture_free((AVPicture*)ctx->yuv420_pic);
        av_frame_free(&ctx->yuv420_pic);
    }            
    if (ctx->sws_ctx)
        sws_freeContext(ctx->sws_ctx);

    if (ctx->rgb24_pic)
        av_frame_free(&ctx->rgb24_pic);            
    if (ctx->rgb24_sws_ctx)
        sws_freeContext(ctx->rgb24_sws_ctx);
}

int vn_capture_start(vn_capture_context_t *ctx, const char *audio_capture_device, const char *video_capture_device,
                     const char *video_capture_params, const char *rtsp_url, int bit_rate)
{
    ctx->stop_flag = false;
    
    std::vector<std::string> video_params;
    int video_width;
    int video_height;
    
    split(video_capture_params, "/", video_params);

    if (video_params.size() < 3)
    {
        fprintf(stderr, "Wrong number of video parameters\n");
        return -1;
    }

    if (sscanf(video_params[0].c_str(), "%dx%d", &video_width, &video_height) != 2)
    {
        fprintf(stderr, "Invalid video size (%s)\n", video_params[0].c_str());
        return -1;
    }
    
    // init streaming
    ResultStatus result;
    ctx->push_client = new PushMediaClient();
    ctx->push_client->initRTSPStream(&result, rtsp_url);

    if (result.errorCode != 0)
    {
        videonext::media::MutexGuard mg(*ctx->callbacks_mutex);
        if (ctx->callbacks.on_error)
        {
            char error[256];
            snprintf(error, sizeof(error), "Init stream failed: %s", result.errorString.c_str());

            mg.release();
            ctx->callbacks.on_error(result.errorCode, error, ctx->callbacks_client_data);
            mg.acquire();
        }
        
        fprintf(stderr, "initStream() failed: %d (%s)\n", result.errorCode, result.errorString.c_str());
        return -1;
    }    
    
    AVDictionary *d = NULL;
    av_dict_set(&d, "video_size", video_params[0].c_str(), 0);
    if (video_params[1] == "I420")
    {
        av_dict_set(&d, "pixel_format", "yuv420p", 0);
    }
    else if (video_params[1] == "YUY2")
    {
        ctx->yuv420_pic = av_frame_alloc();

        if (avpicture_alloc((AVPicture *)ctx->yuv420_pic, AV_PIX_FMT_YUV420P, video_width, video_height) != 0)
        {
            fprintf(stderr, "Picture allocation failed(%s/%s)\n", video_params[0].c_str(), video_params[1].c_str());
            return -1;
        }
        ctx->sws_ctx = sws_getContext(video_width, video_height, AV_PIX_FMT_YUYV422,
                                      video_width, video_height,
                                      AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);

        av_dict_set(&d, "pixel_format", "yuyv422", 0);
    }

    // allocating frame and sws for preview
    if (ctx->callbacks.create_image_preview_buffer)
    {
        ctx->rgb24_pic = av_frame_alloc();

        int preview_width  = video_width;
        int preview_height = video_height;

        if (preview_height > MAX_PREVIEW_HEIGHT)
        {
            preview_height = MAX_PREVIEW_HEIGHT;
            preview_width  = video_width * preview_height / video_height;
            if (preview_width % 32 != 0)
                preview_width = (32 - preview_width % 32 ) + preview_width; // align
        }

        unsigned char *buf = (unsigned char *)ctx->callbacks.create_image_preview_buffer(preview_width, preview_height, ctx->callbacks_client_data);

        avpicture_fill((AVPicture *)ctx->rgb24_pic, buf, AV_PIX_FMT_BGR24, preview_width, preview_height);

        ctx->rgb24_sws_ctx = sws_getContext(video_width, video_height, AV_PIX_FMT_YUV420P,
                                            preview_width, preview_height,
                                            AV_PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    }

    av_dict_set(&d, "audio_buffer_size", "24", 0);
    av_dict_set(&d, "sample_rate", "16000", 0);
//    av_dict_set(&d, "sample_size", "16", 0);
    av_dict_set(&d, "channels", "2", 0);

    
    av_dict_set(&d, "frame_rate", video_params[2].c_str(), 0);
    int rtbufsize = 1 /*secs*/ * video_height * video_width * atoi(video_params[2].c_str()) /*max fps*/ * 2 /*bytes per pixel*/;
    char rtbufsize_str[32];
    snprintf(rtbufsize_str, sizeof(rtbufsize_str), "%d", rtbufsize);
    fprintf(stdout, "Real time buffer size: %s\n", rtbufsize_str);
    av_dict_set(&d, "rtbufsize", rtbufsize_str, 0);
    
    ctx->s = avformat_alloc_context();
    if (!ctx->s)
       return -1;

    ctx->video_frame_rate = 30;
    ctx->video_bit_rate   = bit_rate;
    ctx->video_enc_buf_size = bit_rate;
    ctx->video_width  = video_width;
    ctx->video_height = video_height;

    std::string src = std::string("video=") + video_capture_device + ":audio=" + audio_capture_device;
    if (avformat_open_input(&ctx->s, src.c_str(), ctx->iformat, &d) != 0)
    {
        fprintf(stderr, "avformat_open_input() failed\n");
        return -1;
    }

    if (avformat_find_stream_info(ctx->s, 0) < 0)
    {
        fprintf(stderr, "av_find_stream_info() failed\n");
        return -1;
    }

    if (init_video_encoder(ctx) != 0) 
        return -1;
    
    av_dump_format(ctx->s, 0, src.c_str(), 0);
    
#ifdef USE_THREADS
#ifdef WIN32
      ctx->thr = CreateThread(NULL, 0, &vn_capture_thread, ctx, 0, NULL);
      if (ctx->thr == NULL)
          return -1;
#else
      if (pthread_create(&ctx->thr, 0, vn_capture_thread, ctx))
          return -1;
#endif
#else // !USE_THREADS
      vn_capture_thread(ctx);

      vn_capture_cleanup(ctx);
#endif       
      
    return 0;
}

void vn_capture_stop(vn_capture_context_t *ctx)
{
    ctx->stop_flag = true;

#ifdef USE_THREADS    
    if (ctx->thr)
    {
#ifdef WIN32
        WaitForSingleObject(ctx->thr, INFINITY);
#else
        pthread_join(ctx->thr, NULL);
#endif
    }

    ctx->thr = 0;

    vn_capture_cleanup(ctx);
    
#endif    
}

void vn_capture_destroy(vn_capture_context_t *ctx)
{
    vn_capture_stop(ctx);

    if (ctx->audio_devices)
    {
        char **devices = ctx->audio_devices;
        for (char **audio_device = devices; *audio_device != NULL; audio_device++)
        {
            free(*audio_device);
        }
        free(ctx->audio_devices);

        ctx->audio_devices = 0;
    }

    if (ctx->video_devices)
    {
        char **devices = ctx->video_devices;
        for (char **video_device = devices; *video_device != NULL; video_device++)
        {
            free(*video_device);
        }
        free(ctx->video_devices);
        ctx->video_devices = 0;

    }

    if (ctx->video_resolutions)
    {
        char **res = ctx->video_resolutions;
        for (char **r = res; *r; r++)
        {
            free(*r);
        }

        free(ctx->video_resolutions);
        ctx->video_resolutions = 0;
    }
    
    if (ctx->callbacks_mutex)
        delete ctx->callbacks_mutex;

    free(ctx);
}




