/* The source code for the C++ Mpeg class */ /* (c)1994 Alexis 'Milamber' Ashley */ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include "mpeg.H" #include "decoder.h" #include "huffman.h" #include "util.h" #include "iframe.h" #include "pframe.h" #include "bframe.h" #include "fsize.h" #include "basic_frame.h" #include "search.h" #include "psearch.h" #include "byteorder.h" #include "block.h" const int Mpeg::fps_table[16]={0,24,24,25,30,30,50,60,60,0,0,0,0,0,0,0}; const char Mpeg::pattern[pattern_size]="IBBPBBP"; const Mpeg::frame_type Mpeg::FrameTypes[Mpeg::max_frame_type+1]= {Bad_Frame,I_Frame,P_Frame,B_Frame}; Mpeg::Mpeg(void) { init_tables(); // Initialise the Huffman decoding tables callback = NULL; mpegname = NULL; } Mpeg::~Mpeg(void) { Close(); // Close the mpeg file (in case it's still open) & close // the temp file & delete it. } /* *-------------------------------------------------------------- * * Open -- * * Opens the file specified in "filename". If file open was successful, * it parses the mpeg header, and calls various functions to setup the * structures required by the decoder module. It also creates a temp * file. * * Results: * Returns "file_not_found" if unable to open the file. * Returns "invalid_file" if the file isn't an mpeg stream * Returns "okay" if opened successfully. * * Side effects: * Bit stream is parsed to read the header. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::Open(const char *filename) { mpeg_stream = NewVidStream(64000); // Create the decoder's data // structures. assert(mpeg_stream!=NULL); // assert that NewVidStream was successful if(mpeg_stream!=NULL) { Close(); } mpegname = new char[strlen(filename)+1]; if(mpegname==NULL) return(out_of_memory); strcpy(mpegname,filename); if(open_file(filename,mpeg_stream)!=0) // Open the file in the decoder return(file_not_found); if(ParseHeader(mpeg_stream)!=0) return(invalid_file); mio.open(filename,ios::in); // Open the file using the bstream class tmpnam(tempname); // Get stdio to make me a temp file name tio.open(tempname,ios::in | ios::out | ios::noreplace); int frame_size = mpeg_stream->v_size*mpeg_stream->h_size; frame_size = (frame_size + frame_size/2)*sizeof(byte); cache_size = (CACHE_SIZE*1024)/frame_size; cout << "Cache will hold upto " << cache_size << " frames\n"; for(long l=0; lmax_frame_type || datapicture_rate]; abs_addr abs = 360000*(hms.hours%60) + 6000*(hms.mins%60) + 100*(hms.secs%60) + (hms.hsecs%100); abs *= fps; abs /= 100; return(Seek(abs)); } /* *-------------------------------------------------------------- * * NextFrame -- * Moves the 'pointer' to the next frame. * * Results: * Returns "bad_seek" if unable to do the seek. * Returns "okay" if seek successfull. * * Side effects: * See seek(abs_addr) * *-------------------------------------------------------------- */ Mpeg::status Mpeg::NextFrame(void) { if(!mio.is_open()) return(invalid_file); if(FList.Advance()==false || FList.At_End()==true) return(bad_seek); return(okay); } /* *-------------------------------------------------------------- * * PreviousFrame -- * Moves the 'pointer' to the previous frame. * * Results: * Returns "bad_seek" if unable to do the seek. * Returns "okay" if seek successfull. * * Side effects: * See seek(abs_addr) * *-------------------------------------------------------------- */ Mpeg::status Mpeg::PreviousFrame(void) { if(!mio.is_open()) return(invalid_file); if(FList.Previous()==false) return(bad_seek); return(okay); } /* *-------------------------------------------------------------- * * GetPos_abs -- * Returns the current pos, as an absoulte frame reference * * Results: * See above * * Side effects: * None * *-------------------------------------------------------------- */ Mpeg::abs_addr Mpeg::GetPos_abs(void) const { return(FList.Get_Pos()); } /* *-------------------------------------------------------------- * * GetPos_hms -- * Returns the current pos, as an hours, mins & secs timecode * * Results: * See above * * Side effects: * None * *-------------------------------------------------------------- */ Mpeg::hms_addr Mpeg::GetPos_hms(void) const { hms_addr hms; int fps = fps_table[mpeg_stream->picture_rate]; long hsecs = FList.Get_Pos()*100 / fps; hms.hours = (unsigned short int)(hsecs/360000); hsecs %= 360000; hms.mins = (unsigned short int)(hsecs/6000); hsecs %= 6000; hms.secs = (unsigned short int)(hsecs/100); hms.hsecs = (unsigned short int)(hsecs%100); return(hms); } /* *-------------------------------------------------------------- * * Read -- * Reads from the mpeg stream/temp file one frame, which it puts in * the existing frame class, which was passed as a parameter. * * Results: * Returns "bad_frame" if unable to read from the mpeg stream. * Returns "unknown_error" if unable to copy to the frame. * Returns "okay" if read successfully. * * Side effects: * May need to clear part of the cache. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::Read(frame &fr) { status ret_stat; if(!mio.is_open()) // If file isn't open, return an error return(invalid_file); if(FList.At_End()) // If at end of frame list, return eof return(end_of_file); Fstruct Fs = FList.Read(); // Read Fstruct data for this frame if(Fs.cache!=NULL) // If cached, return the cached frame { fr = *Fs.cache; return(okay); } if(Fs.location==MpegFile) ret_stat = MpegRead(Fs.list_pos,fr); else ret_stat = TempRead(Fs.list_pos,fr); CheckCache(); frame_cache.Write(FList.Get_Pos()); // Append current frame to cache frame_cache.Advance(); Fs.cache = new frame(fr); // Copy frame to cache FList.Write(Fs); // Update FList return(ret_stat); } /* *-------------------------------------------------------------- * * MpegRead -- * Reads from the mpeg stream one frame, which it puts in * a new frame class, which it allocates, and then returns. * * Results: * Returns "bad_frame" if unable to read from the mpeg stream. * Returns "unknown_error" if unable to copy to the frame. * Returns "okay" if read successfully. * Returns "end_of_file" if read sucessfully, but we've reached EOF * * Side effects: * Mpeg bit stream is parsed to read the frame. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::MpegRead(long int pos,frame &Frame) { assert(mpeg_stream!=NULL); // The constructor should have failed on an // assert if this structure hadn't been created, // so this is a bit of a paranoia assert ! for(long l=0; lEOF_Flag) return(end_of_file); return(okay); } } // **** Move to the correct position in the mpeg file, if necessary if( posMList.Get_Pos()+20 ) { PictImage *img; MpegFrame MF, MF2; int count=0; // count of the no. of frames we've had to backtrack by int refs=0; // This is the number of reference frames we need to // decode before decoding the required frame. MList.Set_Pos(pos); MF = MList.Read(); if(MF.type==P_Frame || MF.type==B_Frame) refs=1; else refs=0; while(refs>0) // If the frame isn't an I frame, move backwards down { // the list until we find an I frame. MList.Previous(); MF = MList.Read(); count++; if(MF.type==I_Frame) refs--; } seek_file(mpeg_stream,MF.file_pos); // Call decoder module's seek fn img = GetPictImage(mpeg_stream); // do a read, because there's a 1 frame decoding delay for(long i=0; iEOF_Flag) return(end_of_file); return(okay); } /* *-------------------------------------------------------------- * * TempRead -- * Reads from the temp file one frame, which it puts in * a new frame class, which it allocates, and then returns. * * Results: * Returns "bad_frame" if unable to read from the temp file * Returns "unknown_error" if unable to copy to the frame. * Returns "okay" if read successfully. * Returns "end_of_file" if read sucessfully, but we've reached EOF * * Side effects: * The temp file is read from. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::TempRead(long int pos,frame &Frame) { TempFrame TF; // if(pos!=TList.Get_Pos()) // If not already at the correct position { TList.Set_Pos(pos); // Move TList to the location pos TF = TList.Read(); // Read the structure stored there tio.seekg(TF.file_pos); // Seek to the position given in TF } tio >> Frame; // Read the frame TList.Advance(); // Advance current pos in TList return(okay); } /* *-------------------------------------------------------------- * * Height -- * * Results: * Returns height of mpeg, in pixels * * Side effects: * None * *-------------------------------------------------------------- */ unsigned int Mpeg::Height(void) const { if(!mio.is_open()) return(0); return(mpeg_stream->v_size); } /* *-------------------------------------------------------------- * * Width -- * * Results: * Returns with of mpeg, in pixels * * Side effects: * None * *-------------------------------------------------------------- */ unsigned int Mpeg::Width(void) const { if(!mio.is_open()) return(0); return(mpeg_stream->h_size); } /* *-------------------------------------------------------------- * * Length -- * * Results: * Returns length of mpeg file, is the total number of frames * in the mpeg * * Side effects: * None * *-------------------------------------------------------------- */ Mpeg::abs_addr Mpeg::Length(void) const { if(!mio.is_open()) return(0); return(file_length); } /* *-------------------------------------------------------------- * * Running time -- * * Results: * Returns the length of the mpeg, in terms of its hours, * mins, secs and hsecs timecode * * Side effects: * None * *-------------------------------------------------------------- */ Mpeg::hms_addr Mpeg::RunningTime(void) const { hms_addr hms; if(!mio.is_open()) { hms.hours = hms.mins = hms.secs = hms.hsecs = 0; return(hms); } int fps = fps_table[mpeg_stream->picture_rate]; abs_addr abs = Length(); long secs = abs*100 / fps; hms.hours = (unsigned short int)(secs/360000); secs %= 360000; hms.mins = (unsigned short int)(secs/6000); secs %= 6000; hms.secs = (unsigned short int)(secs/100); hms.hsecs = (unsigned short int)(secs%100); return(hms); } /* *-------------------------------------------------------------- * * FrameRate-- * * Results: * Returns framerate * * Side effects: * None * *-------------------------------------------------------------- */ int Mpeg::FrameRate(void) const { if(!mio.is_open()) return 0; return fps_table[mpeg_stream->picture_rate]; } /* *-------------------------------------------------------------- * * Write -- * Writes the frame to the mpeg, overwriting the current frame * * Returns: * invalid_file if file not open * end_of_file if at end of file (frame isn't written) * okay if okay (frame has been written) * * Side effects: * Appends the data to the end of the temp file, changes FList, * so that it points to the temp file for this frame. Updates * the cache, if necessary. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::Write(const frame &Frame) { Fstruct Fs; // Create a Fstruct structure TempFrame TF; // Create a TempFrame structure if(!mio.is_open()) // Return if file not open return(invalid_file); if(FList.At_End()) // Return if at end of file return(end_of_file); while(TList.At_End()==false) TList.Advance(); // Move to the end of TList // tio.seekp(0,ios::end); // Move to the end of the file TF.file_pos = tio.tellp(); // Store the current file pointer Fs = FList.Read(); // Read current FList contents TF.prev_loc = Fs.location; // Store the current frame location TF.prev_list_pos=Fs.list_pos; // Store the current frame position tio << Frame; // Write to the temp file TList.Write(TF); // write TF into the TList Fs.location = TempFile; // Set the location to TempFile Fs.list_pos = TList.Get_Pos(); // Tell it where to find TempFrame data if(Fs.cache!=NULL) // Update the cache if necessary { *Fs.cache = Frame; } FList.Write(Fs); // Write Fs to FList TList.Advance(); // Move TList forward if(callback!=NULL) (*callback)(callback_data,FList.Get_Pos()); return(okay); } /* *-------------------------------------------------------------- * * Undo -- * Undoes the last write command to the current frame * * Returns: * invalid_file if file not open * end_of_file if at end of file * bad_frame if the frame cannot be undone (ie there's been no changes) * okay if okay (frame has been undone) * * Side effects: * changes FList, invalidates the cache entry, if necessary. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::Undo(void) { Fstruct Fs; // Create a Fstruct structure TempFrame TF; // Create a TempFrame structure long curpos; if(!mio.is_open()) // Return if file not open return(invalid_file); if(FList.At_End()) // Return if at end of file return(end_of_file); Fs = FList.Read(); // Read current FList contents if(Fs.location==MpegFile) return bad_frame; // Return if the frame doesn't need undoing TList.Set_Pos(Fs.list_pos); TF = TList.Read(); if(TF.prev_list_pos==-1) // If this frame is an inserted frame, { // delete the frame Delete(); return(okay); } Fs.location = TF.prev_loc; Fs.list_pos = TF.prev_list_pos; if(Fs.cache!=NULL) delete Fs.cache; Fs.cache = NULL; FList.Write(Fs); curpos = FList.Get_Pos(); frame_cache.Rewind(); while(frame_cache.At_End()==false) { if(frame_cache.Read()==curpos) { frame_cache.Delete(); break; } frame_cache.Advance(); } return(okay); } /* *-------------------------------------------------------------- * * Delete -- * Deletes the frame at the current position * * Returns: * invalid_file if file not open * end_of_file if currently past the end of the frame list * unknown_error if unable to delete from FList * okay if okay * * Side effects: * FList is updated. * *-------------------------------------------------------------- */ Mpeg::status Mpeg::Delete(void) { long pos,pos2; if(!mio.is_open()) // Return if file not open return(invalid_file); if(FList.At_End()) // Return if no frame to delete return(end_of_file); Fstruct Fs = FList.Read(); // Read Fstruct data for this frame pos=FList.Get_Pos(); // Get current FList position if(Fs.cache!=NULL) // If cached, delete the cached frame { delete Fs.cache; } if(FList.Delete()==false) return(unknown_error); frame_cache.Rewind(); while( !frame_cache.At_End() ) { pos2=frame_cache.Read(); if(pos2==pos) // If deleted frame was in cache, frame_cache.Delete(); // delete entry from frame_cache else if(pos2>pos) { pos2--; frame_cache.Write(pos2); } frame_cache.Advance(); } file_length--; return(okay); } /* *-------------------------------------------------------------- * * Insert -- * Inserts no_of_frames frames before the current position * * Returns: * invalid_file if file not open * bad_frame if asked to insert <1 frames * okay if okay * * Side effects: * None * *-------------------------------------------------------------- */ Mpeg::status Mpeg::Insert(int no_of_frames, frame **f_array) { Fstruct Fs; TempFrame TF; long pos,pos2; if(!mio.is_open()) return(invalid_file); if(no_of_frames<1) return(bad_frame); while(TList.At_End()==false) TList.Advance(); // Move to the end of TList // tio.seekp(0,ios::end); // Move to the end of the temp file for(int i=0; ipos) // If frame_cache number > end of insert point, { // the frame_cache will need updating pos2 += long(no_of_frames); frame_cache.Write(pos2); } frame_cache.Advance(); } return(okay); } /* *-------------------------------------------------------------- * * MakeNewFile -- * * Results: * Creates a new mpeg, with the name specified. * * Side effects: * Lots * Reads large quantities of data from mio and tio. Scans * FList and MList at least once. Lets face it, this routine * uses the whole damn shooting match! * *-------------------------------------------------------------- */ Mpeg::status Mpeg::MakeNewFile(const char *filename) { obstream new_mpeg; MpegFrame MF; Ostruct OS; enum {orig_file,new_file} previous; long last_frame=0; int cur_pos=0; long currentGOP=gopSize; long last_GOP=0; long first_B=0; long old_f_pos = FList.Get_Pos(); // Store the old F pos, as this will // need to be restored at the end if(!mio.is_open()) // If file not open, return return(invalid_file); if(strcmp(mpegname,filename)==0) // If trying to overwrite the current return(invalid_file); // file, return. FList.Rewind(); previous=new_file; // *** First phase, make OList, which works out what type each frame *** // *** needs to be, based on (i) The original file, (ii) The edit points *** // *** and (iii) The pattern used for a sequence of new frames. *** do { OS.FS = FList.Read(); OS.pos = abs_addr(FList.Get_Pos()); if(OS.FS.location==MpegFile) { if(previous==new_file) // Check if the previous frame was a { // new frame. If so, make sure that Ostruct OS2; // we start with an I frame. OS.ftype = I_Frame; OList.Write(OS); last_frame = OS.FS.list_pos; cur_pos=0; MList.Set_Pos(OS.FS.list_pos); MF = MList.Read(); if(MF.type!=B_Frame) // If this frame was an I or P frame, previous=orig_file; // all frames after this are 'safe' OList.Previous(); // Now check if the previous frame was OS2 = OList.Read(); // a B frame. If so, change it to a P if(OS2.ftype==B_Frame) // frame, to avoid visual artifacts. { OS2.ftype = P_Frame; OList.Write(OS2); } OList.Previous(); // Now check if the previous frame to OS2 = OList.Read(); // the one above is a B frame. if(OS2.ftype==B_Frame) { OS2.ftype = P_Frame; OList.Write(OS2); } OList.Advance(); OList.Advance(); } else { MList.Set_Pos(OS.FS.list_pos); MF = MList.Read(); if((last_frame+1)==OS.FS.list_pos) // Check for cut frames, { // because they may have OS.ftype = MF.type; // been reference frames last_frame++; } else { if(MF.type==I_Frame) // If a frame has been last_frame = OS.FS.list_pos; // missed, recode all // frames as I's, until an OS.ftype = I_Frame; // I frame is found } OList.Write(OS); } OList.Advance(); } else { if(previous==orig_file) // If the previous frames were from { // the original mpeg, check to see long old_pos; // if they were B frames. If so, Ostruct OS2; // change them to P frames. old_pos = OList.Get_Pos(); do { OList.Previous(); OS2 = OList.Read(); if(OS2.ftype==B_Frame) { OS2.ftype=P_Frame; OList.Write(OS2); OS2.ftype=B_Frame; } } while(OS2.ftype==B_Frame); OList.Set_Pos(old_pos); previous=new_file; } switch(pattern[cur_pos]) { case 'I': OS.ftype=I_Frame; break; case 'P': OS.ftype=P_Frame; break; case 'B': OS.ftype=B_Frame; break; } OList.Write(OS); OList.Advance(); cur_pos = (cur_pos+1) % pattern_size; } FList.Advance(); } while(FList.At_End()==false); int no_of_frames = OList.Get_Pos(); // Setup the parameters for the encoder module Fsize_Reset(); // Reset the frame size Fsize_Note(0,Width(),Height()); // Sets the frame size SetSlicesPerFrame(8); // Sets number of slices per frame SetBlocksPerSlice(); // Uses data stored by SetSlicesPerFrame SetIQScale(4); // Sets the Q scale for I frames SetPQScale(4); // Sets the Q scale for P frames SetBQScale(8); // Sets the Q scale for B frames SetSearchRange(20); // Sets max range to search for motion vectors SetPixelSearch("HALF"); // Use half pixel searches SetPSearchAlg("LOGARITHMIC"); // Use the logarithmic algorithm SetBSearchAlg("CROSS2"); // Use the "CROSS2" algorithm for B frames SetFCode(); // Calculates some values based on the above ResetIFrameStats(); // Resets the time estimate for I frames ResetPFrameStats(); // Resets the time estimate for P frames ResetBFrameStats(); // Resets the time estimate for B frames Frame_Init(); // Creates the EncoderMpegFrame structures // *** Phase II, write the new file, using the information in OList, *** // *** MList and TList. This is where things get complicated! *** new_mpeg.open(filename,ios::out | ios::trunc); if(new_mpeg.fail()) return(unknown_error); WriteSequenceHeader(&new_mpeg); // Write the sequence start header OList.Rewind(); for(long i=0; i= gopSize) // Check to see if it { // is time for another int closed = (first_B==0) ? 1 : 0; // GOP header currentGOP -= gopSize; last_GOP = OList.Get_Pos(); if(first_B!=0) last_GOP -= OList.Get_Pos()-first_B; WriteGOPHeader(&new_mpeg,last_GOP,closed); } if(callback!=NULL) // If a callback has been registered, (*callback)(callback_data,i); // call it. // Encode/Copy this I or P frame CopyOrEncodeFrame(&new_mpeg,OS,OList.Get_Pos()-last_GOP); if(first_B!=0) // Check to see if any B { // frames can now be sent long curpos=OList.Get_Pos(); OList.Set_Pos(first_B); for(long l=first_B; l=cache_size) // If the cache is full, nuke { // CACHE_GRANULARITY frames Fstruct Fs; long cur_pos = FList.Get_Pos(); // Store current FList position frame_cache.Rewind(); for(int i=0; iv_size==Frame->height() && mpeg_stream->h_size==Frame->width()) // If img and Frame are the { // same size, use the quick Frame->set_lum(img->luminance); // method Frame->set_Cr(img->Cr); Frame->set_Cb(img->Cb); } else // else use the slow method { int x,y; byte *lum1; byte *lum2; byte *cr; byte *cb; int x_offset=0; int y_offset=0; int dh = Frame->height()-1; int dw = Frame->width()-1; int sh = mpeg_stream->v_size-1; int sw = mpeg_stream->h_size-1; if(shluminance+y*mpeg_stream->h_size; lum2=lum1+mpeg_stream->h_size; cr=img->Cr+y*mpeg_stream->h_size/4; cb=img->Cb+y*mpeg_stream->h_size/4; if(y>=y_offset && y=x_offset && xLum(x ,y ) = *(lum1++); Frame->Lum(x+1,y ) = *(lum1++); Frame->Lum(x ,y+1) = *(lum2++); Frame->Lum(x+1,y+1) = *(lum2++); Frame->Cr(x>>1,y>>1) = *(cr++); Frame->Cb(x>>1,y>>1) = *(cb++); } else { Frame->Lum(x ,y ) = 0; Frame->Lum(x+1,y ) = 0; Frame->Lum(x ,y+1) = 0; Frame->Lum(x+1,y+1) = 0; Frame->Cr(x>>1,y>>1) = 128; Frame->Cb(x>>1,y>>1) = 128; } } } else { for(x=0; xLum(x ,y ) = 0; Frame->Lum(x+1,y ) = 0; Frame->Lum(x ,y+1) = 0; Frame->Lum(x+1,y+1) = 0; Frame->Cr(x>>1,y>>1) = 128; Frame->Cb(x>>1,y>>1) = 128; } } } } } /* *-------------------------------------------------------------- * * WriteSequenceHeader -- * * Writes an mpeg sequence header to the file specified. * There must be a sequence header at the beginning of an * mpeg, and nowhere else. * * Returns: * Nothing * * Side effects: * Data is appended to the file. * *-------------------------------------------------------------- */ void Mpeg::WriteSequenceHeader(obstream *file) { int index; file->BitWrite(SEQ_START_CODE,32); // Seq start code file->BitWrite(mpeg_stream->h_size,12); // horizontal size file->BitWrite(mpeg_stream->v_size,12); // vertical size file->BitWrite(1,4); // aspect ratio (set to 1:1) file->BitWrite(mpeg_stream->picture_rate,4); // picture rate file->BitWrite(-1,18); // bit rate (set to variable) file->BitWrite(1,1); // marker bit file->BitWrite(16,10); // "Video Buffering Verifier" buffer // size (set to 20K) int fps = fps_table[mpeg_stream->picture_rate]; // Calculate the constrained parameter flag if(mpeg_stream->h_size<=768 && mpeg_stream->v_size<=576 && ((mpeg_stream->h_size+15)/16)*((mpeg_stream->v_size+15)/16)<=396 && ((mpeg_stream->h_size+15)/16)*((mpeg_stream->v_size+15)/16)*fps<=9900) { file->BitWrite(1,1); // constrained param flag = true } else { file->BitWrite(0,1); // constrained param flag = false } file->BitWrite(1,1); // intra quant matrix (present) for (index = 0; index < 64; index++) { file->BitWrite(mpeg_stream-> intra_quant_matrix[zigzag[index][1]][zigzag[index][0]],8); } file->BitWrite(1,1); // non-intra quant matrix (present) for (index = 0; index < 64; index++) { file->BitWrite(mpeg_stream-> non_intra_quant_matrix[zigzag[index][1]][zigzag[index][0]],8); } file->FlushWrite(); // Flush the rest of the byte } /* *-------------------------------------------------------------- * * WriteSequenceEnder-- * There must be a sequence ender at the end of the mpeg file. * * Returns: * Nothing * * Side effects: * Data is appended to the file. * *-------------------------------------------------------------- */ void Mpeg::WriteSequenceEnder(obstream *file) { file->BitWrite(SEQ_END_CODE,32); } /* *-------------------------------------------------------------- * * WriteGOPHeader -- * This writes a Group Of Pictures header to the mpeg file. * There can be as many (or as few) of these as you want, as * long as there is at least one at the start of the file (after * the sequence header) * * Returns: * Nothing * * Side effects: * Data is appended to the file. * *-------------------------------------------------------------- */ void Mpeg::WriteGOPHeader(obstream *file, long frame_no, int closed) { int fps = fps_table[mpeg_stream->picture_rate]; int hrs,mins,secs,pics; pics = frame_no % fps; frame_no /= fps; secs = frame_no % 60; frame_no /= 60; mins = frame_no % 60; hrs = frame_no / 60; file->BitWrite(GOP_START_CODE,32); // Write header code file->BitWrite(0,1); // Drop frame flag (set to false) file->BitWrite(hrs,5); // Time code : hours file->BitWrite(mins,6); // Time code : mins file->BitWrite(1,1); // Marker bit file->BitWrite(secs,6); // Time code : seconds file->BitWrite(pics,6); // Time code : pictures file->BitWrite(closed,1); // Closed GOP flag file->BitWrite(0,1); // Broken link flag (set to false) file->FlushWrite(); // Flush rest of byte } /* *-------------------------------------------------------------- * * SeekHeader -- * * Results: * Seeks to the header startcode, starting from position pos, * and scanning forward, until the startcode is found. This * routine also consumes the required startcode. * * Side effects: * None * *-------------------------------------------------------------- */ void Mpeg::SeekHeader(ibstream *file, long pos, long startcode) { long code; file->seekg(pos); do { code = NextStartCode(file); } while(code!=startcode && code>0); } /* *-------------------------------------------------------------- * * NextStartCode -- * * Results: * Scans forward, until it finds a startcode prefix (0x000001), * and then consumes the next byte, and returns the startcode * (including the prefix). * Hense: the startcode has been consumed on return. * * Side effects: * None * *-------------------------------------------------------------- */ long Mpeg::NextStartCode(ibstream *file) { byte temp; int state=0; file->clear(); while(state<3) { file->get(temp); if(file->eof()) return -1; if(temp==0) { if(state<2) state++; } else if(state==2 && temp==1) state++; else state=0; } file->read(&temp,1); return long(0x100 | temp); } /* *-------------------------------------------------------------- * * CopyOrEncodeFrame -- * * This routine will copy a frame from the original mpeg, if * it is safe to do so, else it will encode the frame. * * Returns: * Nothing * * Side effects: * "file" is written to, original file may have been read * from. * *-------------------------------------------------------------- */ void Mpeg::CopyOrEncodeFrame(obstream *file, Ostruct &OS, int temp_ref) { if(OS.FS.location==MpegFile) { MList.Set_Pos(OS.FS.list_pos); MpegFrame MF = MList.Read(); if(MF.type==OS.ftype) { CopyFrame(file,MF.file_pos,temp_ref); } else { EncodeFrame(file,OS,temp_ref); } } else { EncodeFrame(file,OS,temp_ref); } } /* *-------------------------------------------------------------- * * CopyFrame -- * Copies the frame from the original mpeg file to the new * mpeg file. * * Returns: * Nothing * * Side effects: * original file is read from, new file is written to * *-------------------------------------------------------------- */ void Mpeg::CopyFrame(obstream *file, long in_file_pos, int temp_ref) { byte tbyte; long temp,temp2; int state=0; bool done=false; file->BitWrite(PICTURE_START_CODE,32); // Write the startcode file->BitWrite(temp_ref,10); // Write the temporal reference SeekHeader(&mio,in_file_pos,PICTURE_START_CODE); // Move mio to the first // byte after PICTURE_START_CODE mio.BitRead(temp,10); // Discard old temporal ref mio.BitRead(temp,3); // Read picture type file->BitWrite(temp,3); // Write picture type mio.BitRead(temp2,16); // Discard old picture rate file->BitWrite(0xFFFF,16); // Write VBV rate as variable if(FrameTypes[temp]==P_Frame || FrameTypes[temp]==B_Frame) { mio.BitRead(temp2,4); // Read the forward vector flag & value file->BitWrite(temp2,4); // Write the forward vector flag & value } if(FrameTypes[temp]==B_Frame) { mio.BitRead(temp2,4); // Read the backward vector flag & value file->BitWrite(temp2,4); // Write the backward vector flag & value } file->BitWrite(0,1); // Write to new mpeg that there is no // extra pict info file->FlushWrite(); // Flush to end of byte temp = NextStartCode(&mio); // Get the next start code if(temp==EXT_START_CODE) // Discard any extension data temp=NextStartCode(&mio); if(temp==USER_START_CODE) // Discard any user data temp=NextStartCode(&mio); if(temp!=SLICE_MIN_START_CODE) // Check that this picture is valid { cerr << "Error: Picture doesn't begin with slice 1\n"; return; } file->BitWrite(SLICE_MIN_START_CODE,32); // Write slice the start code while(!done) // Copy the picture slices from old file to new { mio.read(&tbyte,1); // Read a byte from the mpeg if(!mio.good()) done=true; if(state==3) // Check if this is byte 4 of 4 of a startcode { if( (tbyte | 0x100)SLICE_MAX_START_CODE ) { done=true; // Finish if startcode outside of slice range } else { file->BitWrite(0x100 | tbyte,32); // Write startcode state=0; // Reset state counter } } else if(tbyte==0) // Check for start of a startcode { if(state<2) state++; else file->BitWrite(0,8); } else if(state==2 && tbyte==1) // Check for middle bit of startcode state++; else // Else move to next byte { while(state) // Write any zeros that have been { // consumed file->BitWrite(0,8); state--; } file->write(&tbyte,1); // Write byte } } // End of while loop } /*===========================================================================* * * EncodeFrame * * generates an MPEG stream for the frame passed in OS * * RETURNS: nothing * * SIDE EFFECTS: none * *===========================================================================*/ void Mpeg::EncodeFrame(obstream *file, Ostruct OS, int temp_ref) { struct { Ostruct os; EncoderMpegFrame *mf; } prev,curr,next; BitBucket *bb; bb=Bitio_New(); // Make a new bit bucket curr.os = OS; // if frame is a P frame, need to read in P or I frame before it if ( OS.ftype == P_Frame) { long old_pos = OList.Get_Pos(); OList.Previous(); struct Ostruct OS2=OList.Read(); while(OS2.ftype==B_Frame) { OList.Previous(); OS2 = OList.Read(); } prev.os = OS2; OList.Set_Pos(old_pos); } // if frame is a B frame, need to read in P or I frame before and after it if (OS.ftype == B_Frame ) { long old_pos = OList.Get_Pos(); struct Ostruct OS2=OS; while(OS2.ftype==B_Frame) // Find the first I or P frame before { // this frame OList.Previous(); OS2 = OList.Read(); } prev.os = OS2; OList.Set_Pos(old_pos+1); OS2 = OList.Read(); while(OS2.ftype==B_Frame) // Find the first I or P frame after { // this frame OList.Advance(); OS2 = OList.Read(); } next.os = OS2; OList.Set_Pos(old_pos); } switch(OS.ftype) { case I_Frame: curr.mf = Frame_New(temp_ref,'i'); MakeEncoderFrame(curr.mf,curr.os); GenIFrame(bb, curr.mf); Frame_Free(curr.mf); break; case P_Frame: prev.mf = Frame_New(0,'i'); MakeEncoderFrame(prev.mf,prev.os); Frame_AllocBlocks(prev.mf); BlockifyFrame(prev.mf); curr.mf = Frame_New(temp_ref,'p'); MakeEncoderFrame(curr.mf,curr.os); GenPFrame(bb, curr.mf, prev.mf); Frame_Free(prev.mf); Frame_Free(curr.mf); break; case B_Frame: prev.mf = Frame_New(0,'i'); MakeEncoderFrame(prev.mf,prev.os); Frame_AllocBlocks(prev.mf); BlockifyFrame(prev.mf); curr.mf = Frame_New(temp_ref,'b'); MakeEncoderFrame(curr.mf,curr.os); next.mf = Frame_New(0,'i'); MakeEncoderFrame(next.mf,next.os); Frame_AllocBlocks(next.mf); BlockifyFrame(next.mf); GenBFrame(bb, curr.mf, prev.mf, next.mf); Frame_Free(prev.mf); Frame_Free(curr.mf); Frame_Free(next.mf); break; case D_Frame: cerr << "Sorrym, unable to encode a D-type frame\n"; break; case Bad_Frame: cerr << "Unable to encode a bad frame\n"; break; } // *** Now write the data in bb to the new file *** struct bitBucket *ptr; uint32 buffer[WORDS_PER_BUCKET]; uint32 lastWord; int i; int bitsWritten = 0; int bitsLeft; int numWords; uint8 charBuf[4]; bool flushHere = false; bitsLeft = bb->totalbits; for (ptr = bb->firstPtr; ptr; ptr = ptr->nextPtr) { if (ptr->bitsleftcur == 32 && ptr->currword == 0) { continue; /* empty */ } if ( bitsLeft >= 32 ) { if ( ((ptr->currword + 1) * 32) > bitsLeft ) { numWords = ptr->currword; flushHere = true; } else { numWords = ptr->currword+1; } for (i = 0; i < numWords; i++) { buffer[i] = htonl(ptr->bits[i]); } file->write(buffer,4*numWords); bitsWritten += (numWords * 32); bitsLeft -= (numWords * 32); } else { flushHere = true; } if ( (bitsLeft < 32) && flushHere ) { lastWord = ptr->bits[ptr->currword]; /* output the lastPtr word in big-endian order (network) */ /* now write out lastPtr bits */ while ( bitsLeft > 0 ) { charBuf[0] = (lastWord >> 24); charBuf[0] &= lower_mask[8]; file->write(charBuf, 1); lastWord = (lastWord << 8); bitsLeft -= 8; bitsWritten += 8; } } } Bitio_Free(bb); } /*===========================================================================* * * MakeEncoderFrame * Reads the frame specified in OS, using the Read member function, and * converts this YUV frame from "class frame" to "struct EncoderMpegFrame" * * RETURNS: mf modified * * SIDE EFFECTS: the frame cache may have been changed to allow the * frame to be read. * *===========================================================================*/ void Mpeg::MakeEncoderFrame(EncoderMpegFrame *mf,Ostruct OS) { int y, w, h, w2, h2; byte *lum_p, *Cr_p, *Cb_p; frame Frame(Height(),Width()); Seek(OS.pos); // Seek to the frame specified Read(Frame); // Read the frame w = Frame.width(); h = Frame.height(); w2 = w/2; h2 = h/2; lum_p = Frame.lum_ptr(); // Get the address of the lum plane Cr_p = Frame.Cr_ptr(); // Get the address of the Cr plane Cb_p = Frame.Cb_ptr(); // Get the address of the Cb plane Frame_AllocYCC(mf); // Allocate a YUV frame for(y=0; yorig_y[y],&lum_p[y*w],w); } for(y=0; yorig_cb[y],&Cr_p[y*w2],w2); // the encoder } for(y=0; yorig_cr[y],&Cb_p[y*w2],w2); // the encoder } mf->halfComputed = FALSE; }