diff --git a/codec/h264parser/parser.go b/codec/h264parser/parser.go index 35c8d837..80566b82 100644 --- a/codec/h264parser/parser.go +++ b/codec/h264parser/parser.go @@ -16,6 +16,10 @@ const ( NALU_AUD = 9 ) +const ( + EXTENDED_SAR = 255 +) + func IsDataNALU(b []byte) bool { typ := b[0] & 0x1f return typ >= 1 && typ <= 5 @@ -301,6 +305,9 @@ type SPSInfo struct { CropRight uint CropTop uint CropBottom uint + UnitsInTick uint + TimeScale uint + FixedRate uint Width uint Height uint @@ -309,6 +316,7 @@ type SPSInfo struct { func ParseSPS(data []byte) (self SPSInfo, err error) { r := &bits.GolombBitReader{R: bytes.NewReader(data)} + if _, err = r.ReadBits(8); err != nil { return } @@ -494,6 +502,218 @@ func ParseSPS(data []byte) (self SPSInfo, err error) { self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2 self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2 + + var vui_parameters_present_flag uint + if vui_parameters_present_flag, err = r.ReadBit(); err != nil { + return + } + + if (vui_parameters_present_flag != 0) { + /* + aspect_ratio_info_present_flag equal to 1 specifies that + aspect_ratio_idc is present. aspect_ratio_info_present_flag + equal to 0 specifies that aspect_ratio_idc is not present. + */ + var aspect_ratio_info_present_flag uint + if aspect_ratio_info_present_flag, err = r.ReadBit(); err != nil { + return + } + + if aspect_ratio_info_present_flag != 0 { + /* + aspect_ratio_idc specifies the value of the sample aspect + ratio of the luma samples. Table E-1 shows the meaning of + the code. When aspect_ratio_idc indicates Extended_SAR, the + sample aspect ratio is represented by sar_width and + sar_height. When the aspect_ratio_idc syntax element is not + present, aspect_ratio_idc value shall be inferred to be + equal to 0. + */ + var aspect_ratio_idc uint + if aspect_ratio_idc, err = r.ReadBits(8); err != nil { + return + } + + switch aspect_ratio_idc { + case 0: + // Unspecified + break; + case 1: + // 1:1 + /* + 1280x720 16:9 frame without overscan + 1920x1080 16:9 frame without overscan (cropped from 1920x1088) + 640x480 4:3 frame without overscan + */ + break; + case 2: + // 12:11 + /* + 720x576 4:3 frame with horizontal overscan + 352x288 4:3 frame without overscan + */ + break; + case 3: + // 10:11 + /* + 720x480 4:3 frame with horizontal overscan + 352x240 4:3 frame without overscan + */ + break; + case 4: + // 16:11 + /* + 720x576 16:9 frame with horizontal overscan + 540x576 4:3 frame with horizontal overscan + */ + break; + case 5: + // 40:33 + /* + 720x480 16:9 frame with horizontal overscan + 540x480 4:3 frame with horizontal overscan + */ + break; + case 6: + // 24:11 + /* + 352x576 4:3 frame without overscan + 540x576 16:9 frame with horizontal overscan + */ + break; + case 7: + // 20:11 + /* + 352x480 4:3 frame without overscan + 480x480 16:9 frame with horizontal overscan + */ + break; + case 8: + // 32:11 + /* + 352x576 16:9 frame without overscan + */ + break; + case 9: + // 80:33 + /* + 352x480 16:9 frame without overscan + */ + break; + case 10: + // 18:11 + /* + 480x576 4:3 frame with horizontal overscan + */ + break; + case 11: + // 15:11 + /* + 480x480 4:3 frame with horizontal overscan + */ + break; + case 12: + // 64:33 + /* + 540x576 16:9 frame with horizontal overscan + */ + break; + case 13: + // 160:99 + /* + 540x576 16:9 frame with horizontal overscan + */ + break; + case EXTENDED_SAR: + if _, err = r.ReadBits(16); err != nil { + return + } + if _, err = r.ReadBits(16); err != nil { + return + } + break; + } + } + + var overscan_info_present_flag uint + if overscan_info_present_flag, err = r.ReadBit(); err != nil { + return + } + + if overscan_info_present_flag != 0 { + //overscan_appropriate_flag + if _, err = r.ReadBit(); err != nil { + return + } + } + + var video_signal_type_present_flag uint + if video_signal_type_present_flag, err = r.ReadBit(); err != nil { + return + } + + if video_signal_type_present_flag != 0 { //video_signal_type_present_flag + if _, err = r.ReadBits(3); err != nil {//video_format + return + } + + if _, err = r.ReadBit(); err != nil {//video_full_range_flag + return + } + + var colour_description_present_flag uint + if colour_description_present_flag, err = r.ReadBit(); err != nil { + return + } + + if colour_description_present_flag != 0 { + if _, err = r.ReadBits(8); err != nil {// colour_primaries + return + } + + if _, err = r.ReadBits(8); err != nil {// transfer_characteristics + return + } + + if _, err = r.ReadBits(8); err != nil {// matrix_coefficients + return + } + } + } + + var chroma_loc_info_present_flag uint + if chroma_loc_info_present_flag, err = r.ReadBit(); err != nil { + return + } + + if chroma_loc_info_present_flag != 0 { + + r.ReadExponentialGolombCode() //chroma_sample_loc_type_top_field ue(v) + r.ReadExponentialGolombCode() //chroma_sample_loc_type_bottom_field ue(v) + } + + var timing_info_present_flag uint + if timing_info_present_flag, err = r.ReadBit(); err != nil { + return + } + + if timing_info_present_flag != 0 { + //num_units_in_tick + if self.UnitsInTick, err = r.ReadBits(32); err != nil { + return + } + + //time_scale + if self.TimeScale, err = r.ReadBits(32); err != nil { + return + } + + //fixed_frame_rate_flag + if self.FixedRate, err = r.ReadBit(); err != nil { + return + } + } + } return } diff --git a/examples/open_probe_file/main.go b/examples/open_probe_file/main.go index 4960fdd9..ea1808ee 100644 --- a/examples/open_probe_file/main.go +++ b/examples/open_probe_file/main.go @@ -5,15 +5,32 @@ import ( "github.com/nareix/joy4/av" "github.com/nareix/joy4/av/avutil" "github.com/nareix/joy4/format" + "github.com/nareix/joy4/codec/h264parser" ) func init() { format.RegisterAll() } +func frameRate(info h264parser.SPSInfo) float64 { + var num uint64 + var fps float64 + + num = 500 * uint64(info.TimeScale) /* 1000 * 0.5 */ + + if info.UnitsInTick != 0 { + fps = (float64(num) / float64(info.UnitsInTick)) / 1000 + } + + return fps; +} + func main() { - file, _ := avutil.Open("projectindex.flv") + file, _ := avutil.Open("/tmp/sintel.flv") + if file == nil { + fmt.Println("could not open input file") + } streams, _ := file.Streams() for _, stream := range streams { if stream.Type().IsAudio() { @@ -25,12 +42,36 @@ func main() { } } - for i := 0; i < 10; i++ { + for i := 0; i < 1000; i++ { var pkt av.Packet var err error if pkt, err = file.ReadPacket(); err != nil { break } + + // Split out the NAL units + nals, _ := h264parser.SplitNALUs(pkt.Data) + for _, nalUnit := range nals { + + if len(nalUnit) == 0 { + continue + } + + // Get the type, check for a SPS header + typ := nalUnit[0] & 0x1f + if typ == 7 { + // Try to parse out the SPS header. + if info, err := h264parser.ParseSPS(nalUnit); err == nil { + fmt.Println("SPSInfo", + frameRate(info), + info.UnitsInTick, + info.TimeScale, + info.FixedRate, + info.Width) + } + } + } + fmt.Println("pkt", i, streams[pkt.Idx].Type(), "len", len(pkt.Data), "keyframe", pkt.IsKeyFrame) } diff --git a/utils/bits/golomb_reader.go b/utils/bits/golomb_reader.go index da57cb2f..d1cc9528 100644 --- a/utils/bits/golomb_reader.go +++ b/utils/bits/golomb_reader.go @@ -8,6 +8,8 @@ type GolombBitReader struct { R io.Reader buf [1]byte left byte + prev_two_bytes uint + emulation_prevention_bytes uint } func (self *GolombBitReader) ReadBit() (res uint, err error) { @@ -15,7 +17,24 @@ func (self *GolombBitReader) ReadBit() (res uint, err error) { if _, err = self.R.Read(self.buf[:]); err != nil { return } + /* + // Emulation prevention three-byte detection. + // If a sequence of 0x000003 is found, skip (ignore) the last byte (0x03). + */ + if self.buf[0] == 0x03 && (self.prev_two_bytes & 0xffff) == 0 { + // Detected 0x000003, skip last byte. + if _, err = self.R.Read(self.buf[:]); err != nil { + return + } + + self.emulation_prevention_bytes++ + /* + // Need another full three bytes before we can detect the sequence again. + */ + self.prev_two_bytes = 0xffff + } self.left = 8 + self.prev_two_bytes = (self.prev_two_bytes << 8) | uint(self.buf[0]) } self.left-- res = uint(self.buf[0]>>self.left) & 1 @@ -63,3 +82,7 @@ func (self *GolombBitReader) ReadSE() (res uint, err error) { } return } + +func (self *GolombBitReader) NumEmulationPreventionBytesRead() uint { + return self.emulation_prevention_bytes +}