#pragma once

// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review
// the SDK documentation for the description of the full license terms, which are also provided in the file
// "NDI License Agreement.pdf" within the SDK or online at http://ndi.link/ndisdk_license. Your use of any
// part of this SDK is acknowledgment that you agree to the SDK license terms. The full NDI SDK may be
// downloaded at http://ndi.video/
//
//***********************************************************************************************************
//
// Copyright (C) 2023-2024 Vizrt NDI AB. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files(the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions :
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//***********************************************************************************************************

// It is important when using video to realize that often you are using difference clocks for different parts
// of the signal chain. Within NDI, the sender can send at the clock rate that it wants and the receiver will
// receive it at that rate. The receiver however is very unlikely to share the exact same clock rate in many
// cases. For instance, bear in mind that computer clocks rely on crystals which while all rated for the same
// frequency are still not exact. If you sending computer has an audio clock that it "thinks" is 48000Hz, to
// the receiver computer that has a different audio clock this might be 48001Hz or 47998Hz. While these
// differences might appear small they accumulate over time and can cause audio to either slightly drift out
// of sync (it is receiving more audio sample than it needs to play back) or might cause audio glitches
// because it is not receiving enough audio samples. While we have described the situation for audio, the
// same exact problem occurs for video sources; it is commonly thought that this can be solved by simply
// having a "frame buffer" and that displaying the "most recently received video frame" will solve these
// timing discrepancies. Unfortunately this is not the case and when it is done because of the variance in
// clock timings, it is very common the video will appear the "jitter" when the sending and receiving clocks
// are almost in alignment. The solution to these problems is to implement a "time base corrector" for the
// video clock which is a device that uses hysteresis to know when the best time is to either drop or insert
// a video frame such that the video is most likely to play back smoothly, and audio should be dynamically
// audio sampled (with a high order resampling filter) to adaptively track any clocking differences.
// Implementing these components is very difficult to get entirely correct under all scenarios and this
// implementation is provided to facilitate this and help people who are building real time video
// applications to receive audio and video without needing to undertake the full complexity of implementing
// such clock devices.
//
// Another way to look at what this class does is that it transforms "push" sources (i.e. NDI sources in
// which the data is pushed from the sender to the receiver) into "pull" sources in which a host application
// is pulling the data down-stream. The frame-sync automatically tracks all clocks to achieve the best video
// performance doing this operation.
//
// In addition to time-base correction operations, these implementations also will automatically detect and
// correct timing jitter that might occur. This will internally correct for timing anomalies that might be
// caused by network, sender or receiver side timing errors caused by CPU limitations, network bandwidth
// fluctuations, etc...
//
// A very common use of a frame-synchronizer might be if you are displaying video on screen timed to the GPU
// v-sync, you should use such a device to convert from the incoming time-base into the time-base of the GPU.
//
// The following are common times that you want to use a frame-synchronizer
//     Video playback on screen : Yes, you want the clock to be synced with vertical refresh.
//     Audio playback through sound card : Yes you want the clock to be synced with your sound card clock.
//     Video mixing : Yes you want the input video clocks to all be synced to your output video clock.
//     Audio mixing : Yes, you want all input audio clocks to be brought into sync with your output
//                    audio clock.
//     Recording a single channel : No, you want to record the signal in it's raw form without
//                                  any re-clocking.
//     Recording multiple channels : Maybe. If you want to sync some input channels to match a master clock
//                                   so that they can be ISO edited, then you might want a frame-sync.

// The type instance for a frame-synchronizer.
struct NDIlib_framesync_instance_type;
typedef struct NDIlib_framesync_instance_type* NDIlib_framesync_instance_t;

// Create a frame synchronizer instance that can be used to get frames from a receiver. Once this receiver
// has been bound to a frame-sync then you should use it in order to receive video frames. You can continue
// to use the underlying receiver for other operations (tally, PTZ, etc...). Note that it remains your
// responsibility to destroy the receiver even when a frame-sync is using it. You should always destroy the
// receiver after the frame-sync has been destroyed.
//
PROCESSINGNDILIB_API
NDIlib_framesync_instance_t NDIlib_framesync_create(NDIlib_recv_instance_t p_receiver);

// Destroy a frame-sync implementation.
PROCESSINGNDILIB_API
void NDIlib_framesync_destroy(NDIlib_framesync_instance_t p_instance);

// This function will pull audio samples from the frame-sync queue. This function will always return data
// immediately, inserting silence if no current audio data is present. You should call this at the rate that
// you want audio and it will automatically adapt the incoming audio signal to match the rate at which you
// are calling by using dynamic audio sampling. Note that you have no obligation that your requested sample
// rate, no channels and no samples match the incoming signal and all combinations of conversions
// are supported.
//
// If you wish to know what the current incoming audio format is, then you can make a call with the
// parameters set to zero and it will then return the associated settings. For instance a call as follows:
//
//     NDIlib_framesync_capture_audio(p_instance, p_audio_data, 0, 0, 0);
//
// will return in p_audio_data the current received audio format if there is one or sample_rate and
// no_channels equal to zero if there is not one. At any time you can specify sample_rate and no_channels as
// zero and it will return the current received audio format.
//
PROCESSINGNDILIB_API
void NDIlib_framesync_capture_audio(
	NDIlib_framesync_instance_t p_instance,
	NDIlib_audio_frame_v2_t* p_audio_data,
	int sample_rate, int no_channels, int no_samples
);
PROCESSINGNDILIB_API
void NDIlib_framesync_capture_audio_v2(
	NDIlib_framesync_instance_t p_instance,
	NDIlib_audio_frame_v3_t* p_audio_data,
	int sample_rate, int no_channels, int no_samples
);

// Free audio returned by NDIlib_framesync_capture_audio.
PROCESSINGNDILIB_API
void NDIlib_framesync_free_audio(NDIlib_framesync_instance_t p_instance, NDIlib_audio_frame_v2_t* p_audio_data);

// Free audio returned by NDIlib_framesync_capture_audio_v2.
PROCESSINGNDILIB_API
void NDIlib_framesync_free_audio_v2(NDIlib_framesync_instance_t p_instance, NDIlib_audio_frame_v3_t* p_audio_data);

// This function will tell you the approximate current depth of the audio queue to give you an indication
// of the number of audio samples you can request. Note that if you should treat the results of this function
// with some care because in reality the frame-sync API is meant to dynamically resample audio to match the
// rate that you are calling it. If you have an inaccurate clock then this function can be useful.
// for instance :
//
//  while(true)
//  {   int no_samples = NDIlib_framesync_audio_queue_depth(p_instance);
//      NDIlib_framesync_capture_audio( ... );
//      play_audio( ... )
//      NDIlib_framesync_free_audio( ... )
//      inaccurate_sleep( 33ms );
//  }
//
// Obviously because audio is being received in real-time there is no guarantee after the call that the
// number is correct since new samples might have been captured in that time. On synchronous use of this
// function however this will be the minimum number of samples in the queue at any later time until
// NDIlib_framesync_capture_audio is called.
//
PROCESSINGNDILIB_API
int NDIlib_framesync_audio_queue_depth(NDIlib_framesync_instance_t p_instance);

// This function will pull video samples from the frame-sync queue. This function will always immediately
// return a video sample by using time-base correction. You can specify the desired field type which is then
// used to return the best possible frame. Note that field based frame-synchronization means that the
// frame-synchronizer attempts to match the fielded input phase with the frame requests so that you have the
// most correct possible field ordering on output. Note that the same frame can be returned multiple times.
//
// If no video frame has ever been received, this will return NDIlib_video_frame_v2_t as an empty (all zero)
// structure. The reason for this is that it allows you to determine that there has not yet been any video
// and act accordingly. For instance you might want to display a constant frame output at a particular video
// format, or black.
//
PROCESSINGNDILIB_API
void NDIlib_framesync_capture_video(
	NDIlib_framesync_instance_t p_instance,
	NDIlib_video_frame_v2_t* p_video_data,
	NDIlib_frame_format_type_e field_type NDILIB_CPP_DEFAULT_VALUE(NDIlib_frame_format_type_progressive)
);

// Free audio returned by NDIlib_framesync_capture_video.
PROCESSINGNDILIB_API
void NDIlib_framesync_free_video(NDIlib_framesync_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data);
