/*=========================================================================
Program: GDCM (Grassroots DICOM). A DICOM library
Copyright (c) 2006-2011 Mathieu Malaterre
All rights reserved.
See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
/*
* This examples takes in a MPEG2 and write out a Video Endoscopic Imagae Storage
* encoded using MPEG2 @ Main Profile
* ref: http://chrisa.wordpress.com/2007/11/21/decoding-mpeg2-information/
* See also:
* http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html#gop
* http://cvs.linux.hr/cgi-bin/viewcvs.cgi/mpeg_mod/README.infompeg?view=markup
* http://www.guru-group.fi/~too/sw/m2vmp2cut/mpeg2info.c
*/
/*
* Provides information about an MPEG2 file, including the duration, frame rate, aspect
* ratio, and resolution. Good information about the MPEG2 file structure that helps
* explain parts of the code can be found here:
* http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html#gop
*
* Copyright (c) 2007 Chris Anderson (chrisa@wordpress.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
using System;
using System.IO;
using gdcm;
public class Mpeg2VideoInfo
{
#region Member Variables
private TimeSpan m_startTime = TimeSpan.Zero;
private TimeSpan m_endTime = TimeSpan.Zero;
private TimeSpan m_duration = TimeSpan.Zero;
private eAspectRatios m_aspectRatio = eAspectRatios.Invalid;
private eFrameRates m_frameRate = 0;
private int m_pictureWidth = 0;
private int m_pictureHeight = 0;
#endregion
#region Constants
private const byte PADDING_PACKET = 0xBE;
private const byte VIDEO_PACKET = 0xE0;
private const byte AUDIO_PACKET = 0xC0;
private const byte SYSTEM_PACKET = 0xBB;
private const byte TIMESTAMP_PACKET = 0xB8;
private const byte HEADER_PACKET = 0xB3;
private const int BUFFER_SIZE = 8162; // 8K buffer
private readonly static TimeSpan EMPTY_TIMESPAN = new TimeSpan(0, 0, -1);
#endregion
#region Enumerations
public enum eFrameRates
{
Invalid,
PulldownNTSC, // 24000d/1001d = 23.976 Hz
Film, // 24 Hz
PAL, // 25 Hz
NTSC, // 30000d/1001d = 29.97 Hz
DropFrameNTSC, // 30 Hz
DoubleRatePAL, // 50 Hz
DoubleRateNTSC, // 59.97 Hz
DoubleRateDropFrameNTSC // 60 Hz
}
public enum eAspectRatios
{
Invalid,
VGA, // 1/1
StandardTV, // 4/3
LargeTV, // 16/9
Cinema // 2.21/1
}
#endregion
#region Constructor
public Mpeg2VideoInfo(string file)
{
ParseMpeg(file);
}
#endregion
#region Public Properties
public TimeSpan StartTime
{
get { return m_startTime; }
}
public TimeSpan EndTime
{
get { return m_endTime; }
}
public TimeSpan Duration
{
get { return m_duration; }
}
public eAspectRatios AspectRatio
{
get { return m_aspectRatio; }
}
public eFrameRates FrameRate
{
get { return m_frameRate; }
}
public int PictureWidth
{
get { return m_pictureWidth; }
}
public int PictureHeight
{
get { return m_pictureHeight; }
}
#endregion
#region Private Functions
///
/// Handles the parsing of the MPEG file and retrieving MPEG data
///
/// The path to the MPEG file to parse
private void ParseMpeg(string file)
{
FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
BinaryReader br = new BinaryReader(fs);
m_startTime = GetStartTimeStampInfo(br);
m_endTime = GetEndTimeStampInfo(br);
m_duration = m_endTime.Subtract(m_startTime);
GetHeaderInfo(br);
br.Close();
fs.Close();
}
///
/// Looks for the first timestamp in the file and returns the value
/// (generally 0:00:00, but get it anyway)
///
/// The binary reader providing random access to the MPEG file data
/// The timestamp value
private TimeSpan GetStartTimeStampInfo(BinaryReader br)
{
TimeSpan startTime = EMPTY_TIMESPAN;
byte[] buffer = new byte[BUFFER_SIZE];
br.BaseStream.Seek(0, SeekOrigin.Begin);
while (startTime == EMPTY_TIMESPAN && br.BaseStream.Position < br.BaseStream.Length)
{
int readBytes = br.Read(buffer, 0, BUFFER_SIZE);
for (int offset = 0; offset < readBytes - 8; offset++)
{
if (IsStreamMarker(ref buffer, offset, TIMESTAMP_PACKET))
{
offset += 4; // Move to the data position which follows the stream header
uint timeStampEncoded = GetData(ref buffer, offset);
startTime = DecodeTimeStamp(timeStampEncoded);
if (startTime != EMPTY_TIMESPAN)
break;
}
}
}
return startTime;
}
///
/// Looks for the first timestamp in the file and returns the value
/// (generally 0:00:00, but get it anyway)
///
/// The binary reader providing random access to the MPEG file data
/// The timestamp value
private TimeSpan GetEndTimeStampInfo(BinaryReader br)
{
TimeSpan endTime = EMPTY_TIMESPAN;
byte[] buffer = new byte[BUFFER_SIZE];
br.BaseStream.Seek(-BUFFER_SIZE, SeekOrigin.End);
while (endTime == EMPTY_TIMESPAN && br.BaseStream.Position > BUFFER_SIZE)
{
int readBytes = br.Read(buffer, 0, BUFFER_SIZE);
for (int offset = readBytes - 8; offset >= 0; offset--)
{
if (IsStreamMarker(ref buffer, offset, TIMESTAMP_PACKET))
{
offset += 4; // Move to the data position which follows the stream header
uint timeStampEncoded = GetData(ref buffer, offset);
endTime = DecodeTimeStamp(timeStampEncoded);
if (endTime != EMPTY_TIMESPAN)
break;
}
}
br.BaseStream.Seek(-BUFFER_SIZE * 2, SeekOrigin.Current);
}
return endTime;
}
///
/// Decodes the timestamp data as encoded in the MPEG file and returns the value
///
/// The encoded timestamp data
/// The decoded timestamp data
private TimeSpan DecodeTimeStamp(uint timeStampEncoded)
{
TimeSpan timeStamp = EMPTY_TIMESPAN;
// Mask out the bits containing the property we are after, then
// shift the data to the right to get its value
int hour = (int)(timeStampEncoded & 0x7C000000) >> 26; // Bits 31 -> 27
int minute = (int)(timeStampEncoded & 0x03F00000) >> 20; // Bits 26 -> 21
int second = (int)(timeStampEncoded & 0x0007E000) >> 13; // Bits 19 -> 14
int frame = (int)(timeStampEncoded & 0x00001F80) >> 7; // Bits 13 -> 8 - not used, but included for completeness
timeStamp = new TimeSpan(hour, minute, second);
return timeStamp;
}
///
/// Obtains the header data located in the MPEG file and decodes it
///
/// The binary reader providing random access to the MPEG file data
private void GetHeaderInfo(BinaryReader br)
{
byte[] buffer = new byte[BUFFER_SIZE];
br.BaseStream.Seek(0, SeekOrigin.Begin);
br.Read(buffer, 0, BUFFER_SIZE);
for (int offset = 0; offset < buffer.Length - 4; offset++)
{
if (IsStreamMarker(ref buffer, offset, HEADER_PACKET))
{
offset += 4; // Move to the data position which follows the stream header
uint headerData = GetData(ref buffer, offset);
// Mask out the bits containing the property we are after, then
// shift the data to the right to get its value
m_pictureWidth = (int)(headerData & 0xFFF00000) >> 20;
m_pictureHeight = (int)(headerData & 0x000FFF00) >> 8;
uint aspectRatioIndex = (headerData & 0x000000F0) >> 4;
uint fpsIndex = headerData & 0x0000000F;
m_aspectRatio = (eAspectRatios)fpsIndex;
m_frameRate = (eFrameRates)fpsIndex;
break;
}
}
}
///
/// Combine 4 bytes of data into an integer
///
/// The buffer containing the data
/// The position within the buffer to get the required 4 bytes of data
/// An integer containing the combined 4 bytes of data
private uint GetData(ref byte[] buffer, int offset)
{
return (uint) ((buffer[offset] << 24) |
(buffer[offset + 1] << 16) |
(buffer[offset + 2] << 8) |
(buffer[offset + 3]));
}
///
/// The MPEG file contains numerous stream markers representing the type of
/// data to follow. This function looks at data at a position in the buffer to
/// determine whether it represents a marker of a specified type
///
/// The buffer containing the data to identify the marker within
/// The position within the buffer to test for the marker
/// The type of marker to match against
/// Whether the specified position contains the specified marker
private bool IsStreamMarker(ref byte[] buffer, int offset, byte markerType)
{
return (buffer[offset] == 0x00 &&
buffer[offset + 1] == 0x00 &&
buffer[offset + 2] == 0x01 &&
buffer[offset + 3] == markerType);
}
#endregion
public static int Main(string[] args)
{
string file1 = args[0];
Mpeg2VideoInfo info = new Mpeg2VideoInfo(file1);
System.Console.WriteLine( info.StartTime );
System.Console.WriteLine( info.EndTime );
System.Console.WriteLine( info.Duration );
System.Console.WriteLine( info.AspectRatio );
System.Console.WriteLine( info.FrameRate );
System.Console.WriteLine( info.PictureWidth );
System.Console.WriteLine( info.PictureHeight );
ImageReader r = new ImageReader();
//Image image = new Image();
Image image = r.GetImage();
image.SetNumberOfDimensions( 3 );
DataElement pixeldata = new DataElement( new gdcm.Tag(0x7fe0,0x0010) );
System.IO.FileStream infile =
new System.IO.FileStream(file1, System.IO.FileMode.Open, System.IO.FileAccess.Read);
uint fsize = gdcm.PosixEmulation.FileSize(file1);
byte[] jstream = new byte[fsize];
infile.Read(jstream, 0 , jstream.Length);
SmartPtrFrag sq = SequenceOfFragments.New();
Fragment frag = new Fragment();
frag.SetByteValue( jstream, new gdcm.VL( (uint)jstream.Length) );
sq.AddFragment( frag );
pixeldata.SetValue( sq.__ref__() );
// insert:
image.SetDataElement( pixeldata );
PhotometricInterpretation pi = new PhotometricInterpretation( PhotometricInterpretation.PIType.YBR_PARTIAL_420 );
image.SetPhotometricInterpretation( pi );
// FIXME hardcoded:
PixelFormat pixeltype = new PixelFormat(3,8,8,7);
image.SetPixelFormat( pixeltype );
// FIXME hardcoded:
TransferSyntax ts = new TransferSyntax( TransferSyntax.TSType.MPEG2MainProfile);
image.SetTransferSyntax( ts );
image.SetDimension(0, (uint)info.PictureWidth);
image.SetDimension(1, (uint)info.PictureHeight);
image.SetDimension(2, 721);
ImageWriter writer = new ImageWriter();
gdcm.File file = writer.GetFile();
file.GetHeader().SetDataSetTransferSyntax( ts );
Anonymizer anon = new Anonymizer();
anon.SetFile( file );
MediaStorage ms = new MediaStorage( MediaStorage.MSType.VideoEndoscopicImageStorage);
UIDGenerator gen = new UIDGenerator();
anon.Replace( new Tag(0x0008,0x16), ms.GetString() );
anon.Replace( new Tag(0x0018,0x40), "25" );
anon.Replace( new Tag(0x0018,0x1063), "40.000000" );
anon.Replace( new Tag(0x0028,0x34), "4\\3" );
anon.Replace( new Tag(0x0028,0x2110), "01" );
writer.SetImage( image );
writer.SetFileName( "dummy.dcm" );
if( !writer.Write() )
{
System.Console.WriteLine( "Could not write" );
return 1;
}
return 0;
}
}