/// <summary>
		/// Start Playback
		/// </summary>
		/// <param name="res"></param>
		public void InitPlayback(VideoBuffer videoBuffer) {
			if (videoBuffer == null) {
				throw new ArgumentNullException("videoBufferDescription");
			}
			VerifyAccess();
			
			TimeSpan renderinterval;
			try {
				int fps = AppDefaults.visualSettings.ui_video_rendering_fps;
				fps = (fps <= 0 || fps > 100) ? 100 : fps;
				renderinterval = TimeSpan.FromMilliseconds(1000 / fps);
			} catch {
				renderinterval = TimeSpan.FromMilliseconds(1000 / 30);
			}

			var cancellationTokenSource = new CancellationTokenSource();
			renderSubscription.Disposable = Disposable.Create(() => {
				cancellationTokenSource.Cancel();
			});
			var bitmap = PrepareForRendering(videoBuffer);
			var cancellationToken = cancellationTokenSource.Token;
			var dispatcher = Application.Current.Dispatcher;
			var renderingTask = Task.Factory.StartNew(() => {
				var statistics = new CircularBuffer<long>(100);
				using (videoBuffer.Lock()) {
					try {
						//start rendering loop
						while (!cancellationToken.IsCancellationRequested) {
							using (var processingEvent = new ManualResetEventSlim(false)) {
								dispatcher.BeginInvoke(() => {
									using (Disposable.Create(() => processingEvent.Set())) {
										if (!cancellationToken.IsCancellationRequested) {
											//update statisitc info
											statistics.Enqueue(Stopwatch.GetTimestamp());
											//evaluate averange rendering fps
											var ticksEllapsed = statistics.last - statistics.first;
											double avgFps = 0;
											if (ticksEllapsed > 0) {
												avgFps = ((double)statistics.length * (double)Stopwatch.Frequency) / (double)ticksEllapsed;
											}
											//render farme to screen
											DrawFrame(bitmap, videoBuffer, avgFps);
										}
									}
								});
								processingEvent.Wait(cancellationToken);
							}
							cancellationToken.WaitHandle.WaitOne(renderinterval);
						}
					} catch (OperationCanceledException) {
						//swallow exception
					} catch (Exception error) {
						dbg.Error(error);
					}
				}
			}, cancellationToken);
			
		}
		private void DrawFrame(WriteableBitmap bitmap, VideoBuffer videoBuffer, double averangeFps) {
			VerifyAccess();
			if (isPaused) {
				return;
			}

			bitmap.Lock();
			try {
				using (var ptr = videoBuffer.Lock()) {
					bitmap.WritePixels(
						new Int32Rect(0, 0, videoBuffer.width, videoBuffer.height),
						ptr.value, videoBuffer.size, videoBuffer.stride,
						0, 0
					);
				}
			} finally {
				bitmap.Unlock();
			}
			fpsCaption.Text = averangeFps.ToString("F1");
		}
		/// <summary>
		/// Initiate rendering loop
		/// </summary>
		/// <param name="videoBuffer"></param>
		public void InitPlayback(VideoBuffer videoBuffer) {
			if (videoBuffer == null) {
				throw new ArgumentNullException("videoBufferDescription");
			}
			VerifyAccess();

			TimeSpan renderinterval;
			try {
				int fps = AppDefaults.visualSettings.ui_video_rendering_fps;
				fps = (fps <= 0 || fps > 100) ? 100 : fps;
				renderinterval = TimeSpan.FromMilliseconds(1000 / fps);
			} catch {
				renderinterval = TimeSpan.FromMilliseconds(1000 / 30);
			}

			var cancellationTokenSource = new CancellationTokenSource();
			renderSubscription.Disposable = Disposable.Create(() => {
				cancellationTokenSource.Cancel();
			});
			var bitmap = PrepareForRendering(videoBuffer);
			var cancellationToken = cancellationTokenSource.Token;
			var dispatcher = this.Dispatcher; //Application.Current.Dispatcher;
			var renderingTask = Task.Factory.StartNew(() => {
				var statistics = PlaybackStatistics.Start();
				using (videoBuffer.Lock()) {
					try {
						//start rendering loop
						while (!cancellationToken.IsCancellationRequested) {
							using (var processingEvent = new ManualResetEventSlim(false)) {
								dispatcher.BeginInvoke(() => {
									using (Disposable.Create(() => processingEvent.Set())) {
										if (!cancellationToken.IsCancellationRequested) {
											//update statisitc info
											statistics.Update(videoBuffer);

											//render farme to screen
											DrawFrame(bitmap, videoBuffer, statistics);
										}
									}
								});
								processingEvent.Wait(cancellationToken);
							}
							cancellationToken.WaitHandle.WaitOne(renderinterval);
						}
					} catch (OperationCanceledException) {
						//swallow exception
					} catch (Exception error) {
						dbg.Error(error);
					}
				}
			}, cancellationToken);

		}
		private void DrawFrame(WriteableBitmap bitmap, VideoBuffer videoBuffer, PlaybackStatistics statistics) {
			VerifyAccess();
			if (isPaused) {
				return;
			}
			using (var md = videoBuffer.Lock()) {
				// internally calls lock\unlock, uses MILUtilities.MILCopyPixelBuffer
				bitmap.WritePixels(
					new Int32Rect(0, 0, videoBuffer.width, videoBuffer.height),
					md.value.scan0Ptr, videoBuffer.size, videoBuffer.stride,
					0, 0
				);
			}
			renderingFps.Text = String.Format("rendering fps: {0:F1}", statistics.avgRenderingFps);
			decodingFps.Text = String.Format("decoding fps: {0:F1}", statistics.avgDecodingFps);
			noSignalPanel.Visibility =
				statistics.isNoSignal ? Visibility.Visible : Visibility.Hidden;

		}
			public void Update(VideoBuffer videoBuffer) {
				//update rendering times history
				renderTimes.Enqueue(Stopwatch.GetTimestamp());

				//evaluate averange rendering fps
				avgRenderingFps = CalculateAvgFpsFromTimes(renderTimes);

				//update no signal indicator
				using (var md = videoBuffer.Lock()) {
					signal = md.value.signal;
					md.value.signal = 0;
				}
				UpdateNoSignal();

				//evaluate averange rendering fps
				avgDecodingFps = CalculateAvgFpsFromTimes(decodeTimes);
			}