/* Copyright (c) 2004-2011, John Hurst All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*! \file MPEG2_Parser.cpp \version $Id: MPEG2_Parser.cpp,v 1.11 2011/08/30 17:04:25 jhurst Exp $ \brief AS-DCP library, MPEG2 raw essence reader implementation */ #include #include #include using Kumu::DefaultLogSink; using namespace ASDCP; using namespace ASDCP::MPEG2; // data will be read from a VES file in chunks of this size const ui32_t VESReadSize = 4 * Kumu::Kilobyte; //------------------------------------------------------------------------------------------ // enum ParserState_t { ST_INIT, ST_SEQ, ST_PIC, ST_GOP, ST_EXT, ST_SLICE, }; const char* StringParserState(ParserState_t state) { switch ( state ) { case ST_INIT: return "INIT"; case ST_SEQ: return "SEQ"; case ST_PIC: return "PIC"; case ST_GOP: return "GOP"; case ST_EXT: return "EXT"; case ST_SLICE: return "SLICE"; } return "*UNKNOWN*"; } // class h__ParserState { ParserState_t m_State; ASDCP_NO_COPY_CONSTRUCT(h__ParserState); public: h__ParserState() : m_State(ST_INIT) {} ~h__ParserState() {} inline bool Test_SLICE() { return m_State == ST_SLICE; } inline void Reset() { m_State = ST_INIT; } // inline Result_t Goto_SEQ() { switch ( m_State ) { case ST_INIT: case ST_EXT: m_State = ST_SEQ; return RESULT_OK; case ST_SEQ: case ST_PIC: case ST_GOP: case ST_SLICE: /* Keep gcc quiet */ break; } DefaultLogSink().Error("SEQ follows %s\n", StringParserState(m_State)); return RESULT_STATE; } // inline Result_t Goto_SLICE() { switch ( m_State ) { case ST_PIC: case ST_EXT: m_State = ST_SLICE; return RESULT_OK; case ST_INIT: case ST_SEQ: case ST_GOP: case ST_SLICE: /* Keep gcc quiet */ break; } DefaultLogSink().Error("Slice follows %s\n", StringParserState(m_State)); return RESULT_STATE; } // inline Result_t Goto_PIC() { switch ( m_State ) { case ST_INIT: case ST_SEQ: case ST_GOP: case ST_EXT: m_State = ST_PIC; return RESULT_OK; case ST_PIC: case ST_SLICE: /* Keep gcc quiet */ break; } DefaultLogSink().Error("PIC follows %s\n", StringParserState(m_State)); return RESULT_STATE; } // inline Result_t Goto_GOP() { switch ( m_State ) { case ST_EXT: case ST_SEQ: m_State = ST_GOP; return RESULT_OK; case ST_INIT: case ST_PIC: case ST_GOP: case ST_SLICE: /* Keep gcc quiet */ break; } DefaultLogSink().Error("GOP follows %s\n", StringParserState(m_State)); return RESULT_STATE; } // inline Result_t Goto_EXT() { switch ( m_State ) { case ST_PIC: case ST_EXT: case ST_SEQ: case ST_GOP: m_State = ST_EXT; return RESULT_OK; case ST_INIT: case ST_SLICE: /* Keep gcc quiet */ break; } DefaultLogSink().Error("EXT follows %s\n", StringParserState(m_State)); return RESULT_STATE; } }; //------------------------------------------------------------------------------------------ // This is a parser delagate that reads the stream params from a // sequence header and sequence extension header. The parser is // commanded to return after processing the sequence extension // header. class StreamParams : public VESParserDelegate { h__ParserState m_State; ASDCP_NO_COPY_CONSTRUCT(StreamParams); public: VideoDescriptor m_VDesc; StreamParams() { m_VDesc.ContainerDuration = 0; m_VDesc.ComponentDepth = 8; } ~StreamParams() {} // Result_t Sequence(VESParser*, const byte_t* b, ui32_t) { Result_t result = m_State.Goto_SEQ(); if ( ASDCP_FAILURE(result) ) return result; Accessor::Sequence SEQ(b); m_VDesc.AspectRatio = SEQ.AspectRatio(); m_VDesc.FrameRate = SEQ.FrameRate(); m_VDesc.StoredWidth = SEQ.HorizontalSize(); m_VDesc.StoredHeight = SEQ.VerticalSize(); m_VDesc.BitRate = SEQ.BitRate(); m_VDesc.EditRate = SEQ.Pulldown() ? Rational(SEQ.FrameRate() * 1000, 1001) : Rational(SEQ.FrameRate(), 1); m_VDesc.SampleRate = m_VDesc.EditRate; return RESULT_OK; } // Result_t Extension(VESParser*, const byte_t* b, ui32_t) { Result_t result = m_State.Goto_EXT(); if ( ASDCP_FAILURE(result) ) return result; Accessor::SequenceEx SEQX(b); m_VDesc.ProfileAndLevel = SEQX.ProfileAndLevel(); m_VDesc.FrameLayout = SEQX.Progressive() ? 0 : 1; m_VDesc.CodedContentType = SEQX.Progressive() ? 1 : 2; m_VDesc.LowDelay = SEQX.LowDelay(); m_VDesc.HorizontalSubsampling = SEQX.ChromaFormat() == 3 ? 1 : 2; m_VDesc.VerticalSubsampling = SEQX.ChromaFormat() >= 3 ? 1 : 2; if ( ( m_VDesc.HorizontalSubsampling == 2 ) && ( m_VDesc.VerticalSubsampling == 2 ) ) m_VDesc.ColorSiting = 3; // 4:2:0 else if ( ( m_VDesc.HorizontalSubsampling == 2 ) && ( m_VDesc.VerticalSubsampling == 1 ) ) m_VDesc.ColorSiting = 4; // 4:2:2 else if ( ( m_VDesc.HorizontalSubsampling == 1 ) && ( m_VDesc.VerticalSubsampling == 1 ) ) m_VDesc.ColorSiting = 0; // 4:4:4 // TODO: get H&V size and bit rate extensions return RESULT_FALSE; } Result_t GOP(VESParser*, const byte_t*, ui32_t) { return RESULT_FALSE; } Result_t Picture(VESParser*, const byte_t*, ui32_t) { return RESULT_FALSE; } Result_t Slice(VESParser*, byte_t) { return RESULT_FALSE; } Result_t Data(VESParser*, const byte_t*, i32_t) { return RESULT_OK; } }; //------------------------------------------------------------------------------------------ // This is a parser delagate that reads a VES stream and sets public // instance variables to indicate state. It is used below to read an // entire frame into a buffer. The delegate retains metadata about the // frame for later access. // class FrameParser : public VESParserDelegate { h__ParserState m_State; ASDCP_NO_COPY_CONSTRUCT(FrameParser); public: ui32_t m_FrameSize; bool m_CompletePicture; bool m_HasGOP; bool m_ClosedGOP; ui8_t m_TemporalRef; ui32_t m_PlaintextOffset; FrameType_t m_FrameType; FrameParser() { Reset(); } ~FrameParser() {} void Reset() { m_FrameSize = 0; m_HasGOP = m_ClosedGOP = false; m_CompletePicture = false; m_TemporalRef = 0; m_PlaintextOffset = 0; m_FrameType = FRAME_U; m_State.Reset(); } Result_t Sequence(VESParser*, const byte_t*, ui32_t s) { if ( m_State.Test_SLICE() ) { m_CompletePicture = true; return RESULT_FALSE; } m_FrameSize += s; return m_State.Goto_SEQ(); } Result_t Picture(VESParser*, const byte_t* b, ui32_t s) { if ( m_State.Test_SLICE() ) { m_CompletePicture = true; return RESULT_FALSE; } Accessor::Picture pic(b); m_TemporalRef = pic.TemporalRef(); m_FrameType = pic.FrameType(); m_FrameSize += s; return m_State.Goto_PIC(); } Result_t Slice(VESParser*, byte_t slice_id) { if ( slice_id == FIRST_SLICE ) { m_PlaintextOffset = m_FrameSize; return m_State.Goto_SLICE(); } return m_State.Test_SLICE() ? RESULT_OK : RESULT_FAIL; } Result_t Extension(VESParser*, const byte_t*, ui32_t s) { m_FrameSize += s; return m_State.Goto_EXT(); } Result_t GOP(VESParser*, const byte_t* b, ui32_t s) { Accessor::GOP GOP(b); m_ClosedGOP = GOP.Closed(); m_HasGOP = true; m_FrameSize += s; return m_State.Goto_GOP(); } Result_t Data(VESParser*, const byte_t*, i32_t s) { m_FrameSize += s; return RESULT_OK; } }; //------------------------------------------------------------------------------------------ // The following code assumes the following things: // - each frame will begin with a picture header or a sequence header // - any frame that begins with a sequence header is an I frame and is // assumed to contain a GOP header, a picture header and complete picture data // - any frame that begins with a picture header is either an I, B or P frame // and is assumed to contain a complete picture header and picture data class ASDCP::MPEG2::Parser::h__Parser { StreamParams m_ParamsDelegate; FrameParser m_ParserDelegate; VESParser m_Parser; Kumu::FileReader m_FileReader; ui32_t m_FrameNumber; bool m_EOF; ASDCP::MPEG2::FrameBuffer m_TmpBuffer; ASDCP_NO_COPY_CONSTRUCT(h__Parser); public: h__Parser() : m_TmpBuffer(VESReadSize*8) {} ~h__Parser() { Close(); } Result_t OpenRead(const char* filename); void Close(); Result_t Reset(); Result_t ReadFrame(FrameBuffer&); Result_t FillVideoDescriptor(VideoDescriptor&); }; // Result_t ASDCP::MPEG2::Parser::h__Parser::Reset() { m_FrameNumber = 0; m_EOF = false; m_FileReader.Seek(0); m_ParserDelegate.Reset(); return RESULT_OK; } // void ASDCP::MPEG2::Parser::h__Parser::Close() { m_FileReader.Close(); } // ASDCP::Result_t ASDCP::MPEG2::Parser::h__Parser::OpenRead(const char* filename) { ASDCP_TEST_NULL_STR(filename) ui32_t read_count = 0; Result_t result = m_FileReader.OpenRead(filename); if ( ASDCP_SUCCESS(result) ) result = m_FileReader.Read(m_TmpBuffer.Data(), m_TmpBuffer.Capacity(), &read_count); if ( ASDCP_SUCCESS(result) ) { const byte_t* p = m_TmpBuffer.RoData(); // the mxflib parser demanded the file start with a sequence header. // Since no one complained and that's the easiest thing to implement, // I have left it that way. Let me know if you want to be able to // locate the first GOP in the stream. ui32_t i = 0; while ( p[i] == 0 ) i++; if ( i < 2 || p[i] != 1 || ! ( p[i+1] == SEQ_START || p[i+1] == PIC_START ) ) { DefaultLogSink().Error("Frame buffer does not begin with a PIC or SEQ start code.\n"); return RESULT_RAW_FORMAT; } if ( ASDCP_SUCCESS(result) ) { m_Parser.SetDelegate(&m_ParamsDelegate); result = m_Parser.Parse(p, read_count); } } if ( ASDCP_SUCCESS(result) ) { ui64_t tmp = m_FileReader.Size() / 65536; // a gross approximation m_ParamsDelegate.m_VDesc.ContainerDuration = (ui32_t) tmp; m_Parser.SetDelegate(&m_ParserDelegate); m_FileReader.Seek(0); } if ( ASDCP_FAILURE(result) ) { DefaultLogSink().Error("Unable to identify a wrapping mode for the essence in file \"%s\"\n", filename); m_FileReader.Close(); } return result;} // // ASDCP::Result_t ASDCP::MPEG2::Parser::h__Parser::ReadFrame(FrameBuffer& FB) { Result_t result = RESULT_OK; ui32_t write_offset = 0; ui32_t read_count = 0; FB.Size(0); if ( m_EOF ) return RESULT_ENDOFFILE; // Data is read in VESReadSize chunks. Each chunk is parsed, and the // process is stopped when a Sequence or Picture header is found or when // the input file is exhausted. The partial next frame is cached for the // next call. m_ParserDelegate.Reset(); m_Parser.Reset(); if ( m_TmpBuffer.Size() > 0 ) { memcpy(FB.Data(), m_TmpBuffer.RoData(), m_TmpBuffer.Size()); result = m_Parser.Parse(FB.RoData(), m_TmpBuffer.Size()); write_offset = m_TmpBuffer.Size(); m_TmpBuffer.Size(0); } while ( ! m_ParserDelegate.m_CompletePicture && result == RESULT_OK ) { if ( FB.Capacity() < ( write_offset + VESReadSize ) ) { DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %u\n", FB.Capacity(), ( write_offset + VESReadSize )); return RESULT_SMALLBUF; } result = m_FileReader.Read(FB.Data() + write_offset, VESReadSize, &read_count); if ( result == RESULT_ENDOFFILE || read_count == 0 ) { m_EOF = true; if ( write_offset > 0 ) result = RESULT_OK; } if ( ASDCP_SUCCESS(result) ) { result = m_Parser.Parse(FB.RoData() + write_offset, read_count); write_offset += read_count; } if ( m_EOF ) break; } assert(m_ParserDelegate.m_FrameSize <= write_offset); if ( ASDCP_SUCCESS(result) && m_ParserDelegate.m_FrameSize < write_offset ) { assert(m_TmpBuffer.Size() == 0); ui32_t diff = write_offset - m_ParserDelegate.m_FrameSize; assert(diff <= m_TmpBuffer.Capacity()); memcpy(m_TmpBuffer.Data(), FB.RoData() + m_ParserDelegate.m_FrameSize, diff); m_TmpBuffer.Size(diff); } if ( ASDCP_SUCCESS(result) ) { const byte_t* p = FB.RoData(); if ( p[0] != 0 || p[1] != 0 || p[2] != 1 || ! ( p[3] == SEQ_START || p[3] == PIC_START ) ) { DefaultLogSink().Error("Frame buffer does not begin with a PIC or SEQ start code.\n"); return RESULT_RAW_FORMAT; } } if ( ASDCP_SUCCESS(result) ) { FB.Size(m_ParserDelegate.m_FrameSize); FB.TemporalOffset(m_ParserDelegate.m_TemporalRef); FB.FrameType(m_ParserDelegate.m_FrameType); FB.PlaintextOffset(m_ParserDelegate.m_PlaintextOffset); FB.FrameNumber(m_FrameNumber++); FB.GOPStart(m_ParserDelegate.m_HasGOP); FB.ClosedGOP(m_ParserDelegate.m_ClosedGOP); } return result; } // Fill a VideoDescriptor struct with the values from the file's header. ASDCP::Result_t ASDCP::MPEG2::Parser::h__Parser::FillVideoDescriptor(VideoDescriptor& VDesc) { VDesc = m_ParamsDelegate.m_VDesc; return RESULT_OK; } //------------------------------------------------------------------------------------------ ASDCP::MPEG2::Parser::Parser() { } ASDCP::MPEG2::Parser::~Parser() { } // Opens the stream for reading, parses enough data to provide a complete // set of stream metadata for the MXFWriter below. ASDCP::Result_t ASDCP::MPEG2::Parser::OpenRead(const char* filename) const { const_cast(this)->m_Parser = new h__Parser; Result_t result = m_Parser->OpenRead(filename); if ( ASDCP_FAILURE(result) ) const_cast(this)->m_Parser.release(); return result; } // Rewinds the stream to the beginning. ASDCP::Result_t ASDCP::MPEG2::Parser::Reset() const { if ( m_Parser.empty() ) return RESULT_INIT; return m_Parser->Reset(); } // Places a frame of data in the frame buffer. Fails if the buffer is too small // or the stream is empty. ASDCP::Result_t ASDCP::MPEG2::Parser::ReadFrame(FrameBuffer& FB) const { if ( m_Parser.empty() ) return RESULT_INIT; return m_Parser->ReadFrame(FB); } ASDCP::Result_t ASDCP::MPEG2::Parser::FillVideoDescriptor(VideoDescriptor& VDesc) const { if ( m_Parser.empty() ) return RESULT_INIT; return m_Parser->FillVideoDescriptor(VDesc); } // // end MPEG2_Parser.cpp //