/*========================================================================= 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; } }