/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Tizen.Internals.Errors;
namespace Tizen.Multimedia
{
///
/// Represents a packet for multimedia.
///
public abstract class MediaPacket : IDisposable
{
private IntPtr _handle = IntPtr.Zero;
///
/// Initializes a new instance of the MediaPacket class with the specified media format.
///
/// The media format containing properties for the packet.
/// format is null.
///
/// of the specified format is .
/// Operation failed.
internal MediaPacket(MediaFormat format)
{
if (format == null)
{
throw new ArgumentNullException(nameof(format));
}
if (format.Type == MediaFormatType.Container)
{
throw new ArgumentException("Container format can't be used to create a new packet.");
}
Initialize(format);
_format = format;
}
///
/// Initializes a new instance of the MediaPacket class from a native handle.
///
/// The native handle to be used.
internal MediaPacket(IntPtr handle)
{
_handle = handle;
IntPtr formatHandle;
int ret = Interop.MediaPacket.GetFormat(handle, out formatHandle);
MultimediaDebug.AssertNoError(ret);
try
{
if (formatHandle != IntPtr.Zero)
{
_format = MediaFormat.FromHandle(formatHandle);
}
}
finally
{
Interop.MediaFormat.Unref(formatHandle);
}
}
~MediaPacket()
{
Dispose(false);
}
///
/// Creates and initializes a native handle for the current object.
///
/// The format to be set to the media format.
/// Operation failed.
private void Initialize(MediaFormat format)
{
if (format.Type == MediaFormatType.Container)
{
throw new ArgumentException("Creating a packet for container is not supported.");
}
IntPtr formatHandle = IntPtr.Zero;
try
{
formatHandle = format.AsNativeHandle();
int ret = Interop.MediaPacket.Create(formatHandle, IntPtr.Zero, IntPtr.Zero, out _handle);
MultimediaDebug.AssertNoError(ret);
Debug.Assert(_handle != IntPtr.Zero, "Created handle must not be null");
Alloc();
}
catch (Exception)
{
if (_handle != IntPtr.Zero)
{
Interop.MediaPacket.Destroy(_handle);
_handle = IntPtr.Zero;
}
throw;
}
finally
{
if (formatHandle != IntPtr.Zero)
{
Interop.MediaFormat.Unref(formatHandle);
}
}
}
///
/// Allocates internal buffer.
///
/// Operation failed.
private void Alloc()
{
ErrorCode ret = (ErrorCode)Interop.MediaPacket.Alloc(_handle);
if (ret == ErrorCode.None)
{
return;
}
switch (ret)
{
case ErrorCode.OutOfMemory:
throw new OutOfMemoryException("Failed to allocate buffer for the packet.");
default:
throw new InvalidOperationException("Failed to create a packet.");
}
}
private readonly MediaFormat _format;
///
/// Gets the media format of the current packet.
///
public MediaFormat Format
{
get
{
ValidateNotDisposed();
return _format;
}
}
///
/// Gets or sets the PTS(Presentation Time Stamp) value of the current packet.
///
/// The MediaPacket has already been disposed.
///
/// The MediaPacket is not writable state which means it being used by another module.
public ulong Pts
{
get
{
ValidateNotDisposed();
ulong value = 0;
int ret = Interop.MediaPacket.GetPts(_handle, out value);
MultimediaDebug.AssertNoError(ret);
return value;
}
set
{
ValidateNotDisposed();
ValidateNotLocked();
int ret = Interop.MediaPacket.SetPts(_handle, value);
MultimediaDebug.AssertNoError(ret);
}
}
///
/// Gets or sets the DTS(Decoding Time Stamp) value of the current packet.
///
/// The MediaPacket has already been disposed.
///
/// The MediaPacket is not in writable state which means it being used by another module.
public ulong Dts
{
get
{
ValidateNotDisposed();
ulong value = 0;
int ret = Interop.MediaPacket.GetDts(_handle, out value);
MultimediaDebug.AssertNoError(ret);
return value;
}
set
{
ValidateNotDisposed();
ValidateNotLocked();
int ret = Interop.MediaPacket.SetDts(_handle, value);
MultimediaDebug.AssertNoError(ret);
}
}
///
/// Gets a value indicating whether the packet is encoded type.
///
/// true if the packet is encoded type; otherwise, false.
/// The MediaPacket has already been disposed.
public bool IsEncoded
{
get
{
ValidateNotDisposed();
bool value = false;
int ret = Interop.MediaPacket.IsEncoded(_handle, out value);
MultimediaDebug.AssertNoError(ret);
return value;
}
}
private MediaPacketBuffer _buffer;
///
/// Gets the buffer of the packet.
///
/// The allocated to the packet.
/// This property will return null if the packet is raw video format.
/// The MediaPacket has already been disposed.
///
///
public MediaPacketBuffer Buffer
{
get
{
ValidateNotDisposed();
if (IsVideoPlaneSupported)
{
return null;
}
if (_buffer == null)
{
_buffer = GetBuffer();
}
return _buffer;
}
}
///
/// Gets or sets a length of data written in the .
///
/// The MediaPacket has already been disposed.
///
/// The value specified for this property is less than zero or greater than .
///
/// The MediaPacket has instead of .\n
/// -or-\n
/// The MediaPacket is not in writable state which means it being used by another module.
///
public int BufferWrittenLength
{
get
{
ValidateNotDisposed();
ulong value = 0;
int ret = Interop.MediaPacket.GetBufferSize(_handle, out value);
MultimediaDebug.AssertNoError(ret);
Debug.Assert(value < int.MaxValue);
return (int)value;
}
set
{
ValidateNotDisposed();
ValidateNotLocked();
if (IsVideoPlaneSupported)
{
throw new InvalidOperationException(
"This packet uses VideoPlanes instead of Buffer.");
}
Debug.Assert(Buffer != null);
if (value < 0 || value >= Buffer.Length)
{
throw new ArgumentOutOfRangeException("value must be less than Buffer.Size.");
}
int ret = Interop.MediaPacket.SetBufferSize(_handle, (ulong)value);
MultimediaDebug.AssertNoError(ret);
}
}
private MediaPacketVideoPlane[] _videoPlanes;
///
/// Gets the video planes of the packet.
///
/// The s allocated to the packet.
/// This property will return null if the packet is not raw video format.
/// The MediaPacket has already been disposed.
///
///
public MediaPacketVideoPlane[] VideoPlanes
{
get
{
ValidateNotDisposed();
if (!IsVideoPlaneSupported)
{
return null;
}
if (_videoPlanes == null)
{
_videoPlanes = GetVideoPlanes();
}
return _videoPlanes;
}
}
///
/// Gets or sets the buffer flags of the packet.
///
/// The MediaPacket has already been disposed.
///
/// The MediaPacket is not in writable state which means it being used by another module.
///
public MediaPacketBufferFlags BufferFlags
{
get
{
ValidateNotDisposed();
int value = 0;
int ret = Interop.MediaPacket.GetBufferFlags(_handle, out value);
MultimediaDebug.AssertNoError(ret);
return (MediaPacketBufferFlags)value;
}
set
{
ValidateNotDisposed();
ValidateNotLocked();
int ret = Interop.MediaPacket.ResetBufferFlags(_handle);
MultimediaDebug.AssertNoError(ret);
ret = Interop.MediaPacket.SetBufferFlags(_handle, (int)value);
MultimediaDebug.AssertNoError(ret);
}
}
///
/// Gets a value indicating whether the packet has been disposed of.
///
/// true if the packet has been disposed of; otherwise, false.
public bool IsDisposed
{
get
{
return _isDisposed;
}
}
private bool _isDisposed = false;
///
/// Releases all resources used by the object.
///
///
/// The MediaPacket can not be disposed which means it being used by another module.
///
public void Dispose()
{
if (_isDisposed)
{
return;
}
ValidateNotLocked();
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the resources used by the object.
///
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
///
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
if (_handle != IntPtr.Zero)
{
Interop.MediaPacket.Destroy(_handle);
_handle = IntPtr.Zero;
}
_isDisposed = true;
}
internal IntPtr GetHandle()
{
ValidateNotDisposed();
Debug.Assert(_handle != IntPtr.Zero, "The handle is invalid!");
return _handle;
}
///
/// Validate the current object has not been disposed of.
///
/// The MediaPacket has already been disposed of.
private void ValidateNotDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException("This packet has already been disposed of.");
}
}
///
/// Validate the current object is not locked.
///
/// The MediaPacket has already been disposed of.
/// The MediaPacket is in use by another module.
private void ValidateNotLocked()
{
ValidateNotDisposed();
if (_lock.IsLocked)
{
throw new InvalidOperationException("Can't perform any writing operation." +
"The packet is in use, internally.");
}
}
///
/// Ensures whether the packet is writable.
///
/// The MediaPacket already has been disposed of.
/// The MediaPacket is being used by another module.
internal void EnsureWritableState()
{
ValidateNotDisposed();
ValidateNotLocked();
}
///
/// Ensures whether the packet is readable.
///
/// The MediaPacket already has been disposed of.
internal void EnsureReadableState()
{
ValidateNotDisposed();
}
///
/// Gets a value indicating whether the packet is raw video format.
///
/// true if the packet is raw video format; otherwise, false.
private bool IsVideoPlaneSupported
{
get
{
return !IsEncoded && Format.Type == MediaFormatType.Video;
}
}
///
/// Retrieves video planes of the current packet.
///
/// The s allocated to the current MediaPacket.
private MediaPacketVideoPlane[] GetVideoPlanes()
{
Debug.Assert(_handle != IntPtr.Zero, "The handle is invalid!");
uint numberOfPlanes = 0;
int ret = Interop.MediaPacket.GetNumberOfVideoPlanes(_handle, out numberOfPlanes);
MultimediaDebug.AssertNoError(ret);
MediaPacketVideoPlane[] planes = new MediaPacketVideoPlane[numberOfPlanes];
for (int i = 0; i < numberOfPlanes; ++i)
{
planes[i] = new MediaPacketVideoPlane(this, i);
}
return planes;
}
///
/// Retrieves the buffer of the current packet.
///
/// The allocated to the current MediaPacket.
private MediaPacketBuffer GetBuffer()
{
Debug.Assert(_handle != IntPtr.Zero, "The handle is invalid!");
IntPtr dataHandle;
int ret = Interop.MediaPacket.GetBufferData(_handle, out dataHandle);
MultimediaDebug.AssertNoError(ret);
Debug.Assert(dataHandle != IntPtr.Zero, "Data handle is invalid!");
int size = 0;
ret = Interop.MediaPacket.GetAllocatedBufferSize(_handle, out size);
MultimediaDebug.AssertNoError(ret);
return new MediaPacketBuffer(this, dataHandle, size);
}
#region Lock operations
private readonly LockState _lock = new LockState();
///
/// Provides a thread-safe lock state controller.
///
private sealed class LockState
{
const int LOCKED = 1;
const int UNLOCKED = 0;
private int _locked = UNLOCKED;
internal void SetLock()
{
if (Interlocked.CompareExchange(ref _locked, LOCKED, UNLOCKED) == LOCKED)
{
throw new InvalidOperationException("The packet is already locked.");
}
}
internal void SetUnlock()
{
if (Interlocked.CompareExchange(ref _locked, UNLOCKED, LOCKED) == UNLOCKED)
{
Debug.Fail("The packet to unlock is not locked. " +
"There must be an error somewhere that a lock isn't disposed correctly.");
}
}
internal bool IsLocked
{
get
{
return Interlocked.CompareExchange(ref _locked, 0, 0) == LOCKED;
}
}
}
///
/// Provides a thread-safe lock controller.
///
///
/// using (var lock = BaseMediaPacket.Lock(mediaPacket))
/// {
/// ....
/// }
///
internal sealed class Lock : IDisposable
{
private readonly MediaPacket _packet;
private readonly GCHandle _gcHandle;
private int _lockCount;
internal static Lock Get(MediaPacket packet)
{
Debug.Assert(packet != null);
lock (packet)
{
Lock lck = FromHandle(packet._handle);
if (lck == null)
{
lck = new Lock(packet);
}
lck._lockCount++;
return lck;
}
}
private Lock(MediaPacket packet)
{
Debug.Assert(packet != null, "The packet is null!");
packet.ValidateNotDisposed();
_packet = packet;
_packet._lock.SetLock();
_gcHandle = GCHandle.Alloc(this);
SetExtra(GCHandle.ToIntPtr(_gcHandle));
}
internal static Lock FromHandle(IntPtr handle)
{
Debug.Assert(handle != IntPtr.Zero);
IntPtr extra = GetExtra(handle);
if (extra == IntPtr.Zero)
{
return null;
}
return (Lock)GCHandle.FromIntPtr(extra).Target;
}
private void SetExtra(IntPtr ptr)
{
int ret = Interop.MediaPacket.SetExtra(_packet._handle, ptr);
MultimediaDebug.AssertNoError(ret);
}
private static IntPtr GetExtra(IntPtr handle)
{
IntPtr value;
int ret = Interop.MediaPacket.GetExtra(handle, out value);
MultimediaDebug.AssertNoError(ret);
return value;
}
internal IntPtr GetHandle()
{
return _packet.GetHandle();
}
internal MediaPacket MediaPacket
{
get
{
return _packet;
}
}
private bool _isDisposed = false;
public void Dispose()
{
if (!_isDisposed)
{
lock (_packet)
{
_lockCount--;
if (_lockCount == 0)
{
SetExtra(IntPtr.Zero);
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();
}
//We can assure that at this point '_packet' is always locked by this lock.
_packet._lock.SetUnlock();
}
}
_isDisposed = true;
}
}
}
#endregion
///
/// Creates an object of the MediaPacket with the specified .
///
/// The media format for the new packet.
/// A new MediaPacket object.
public static MediaPacket Create(MediaFormat format)
{
return new SimpleMediaPacket(format);
}
internal static MediaPacket From(IntPtr handle)
{
return new SimpleMediaPacket(handle);
}
}
internal class SimpleMediaPacket : MediaPacket
{
internal SimpleMediaPacket(MediaFormat format) : base(format)
{
}
internal SimpleMediaPacket(IntPtr handle) : base(handle)
{
}
}
}