• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Eyevinn / hi264 / 25463011825

06 May 2026 09:47PM UTC coverage: 87.597% (-0.7%) from 88.264%
25463011825

push

github

tobbee
docs: rewrite README intro around the three primary use cases

Lead with what the toolkit is for instead of how it's built. Three
numbered use cases up front (fMP4 fragment extension that survives
foreign SPS/PPS, scratch-built test content with tunable bitrate, IDR
thumbnail extraction), a 'Why pure Go' section calling out the single
mp4ff dependency and no-cgo / no-FFmpeg posture, then the existing
scope-and-constraints note.

7882 of 8998 relevant lines covered (87.6%)

1.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.62
/pkg/encode/frame_encode.go
1
package encode
2

3
import (
4
        "bytes"
5
        "fmt"
6

7
        "github.com/Eyevinn/mp4ff/avc"
8

9
        "github.com/Eyevinn/hi264/internal/cabac"
10
        "github.com/Eyevinn/hi264/internal/context"
11
        "github.com/Eyevinn/hi264/pkg/yuv"
12
)
13

14
// FrameEncoder encodes a grid-based image as an H.264 IDR frame.
15
type FrameEncoder struct {
16
        Grid            *yuv.Grid
17
        Colors          yuv.ColorMap
18
        Plane           *yuv.PlaneGrid // alternative to Grid+Colors; takes priority if set
19
        QP              int
20
        DisableDeblock  int            // 0=enable, 1=disable
21
        CABAC           bool           // use CABAC entropy coding (Main profile) instead of CAVLC (Baseline)
22
        MaxNumRefFrames int            // max_num_ref_frames in SPS (0 for IDR-only, 1+ for P-frames)
23
        Width           int            // actual pixel width for SPS (0 = Grid.Width*16)
24
        Height          int            // actual pixel height for SPS (0 = Grid.Height*16)
25
        ColorSpace      yuv.ColorSpace // YCbCr matrix standard (default BT601)
26
        Range           yuv.Range      // sample value range (default LimitedRange)
27
        FPS             int            // frame rate for level selection (0 = ignore MBPS)
28
        Kbps            int            // bitrate in kbit/s for level selection (0 = ignore)
29

30
        // SPS/PPS, when non-nil, control IDR slice-header bit widths,
31
        // pic_order_cnt_type, pic_parameter_set_id, and slice_qp_delta
32
        // (compensating for pic_init_qp_minus26). This is what makes a
33
        // stand-alone IDR slice compatible with foreign parameter sets when
34
        // extending an existing stream. When both are nil, hi264's own
35
        // defaults are used (4 bits each for frame_num and POC LSB,
36
        // pps_id=0, pic_init_qp=26).
37
        SPS *avc.SPS
38
        PPS *avc.PPS
39
}
40

41
// idrSliceHeader returns the slice-header fields for an IDR slice using
42
// the encoder's SPS/PPS overrides if set, otherwise hi264's defaults.
43
func (e *FrameEncoder) idrSliceHeader(idrPicID uint32) IDRSliceHeader {
1✔
44
        h := IDRSliceHeader{
1✔
45
                QPDelta:                     int32(e.QP - 26),
1✔
46
                DisableDeblock:              e.DisableDeblock,
1✔
47
                IDRPicID:                    idrPicID,
1✔
48
                PPSID:                       0,
1✔
49
                Log2MaxFrameNumMinus4:       0,
1✔
50
                Log2MaxPicOrderCntLsbMinus4: 0,
1✔
51
                PicOrderCntType:             0,
1✔
52
                DeblockControlPresent:       true,
1✔
53
        }
1✔
54
        if e.SPS != nil {
2✔
55
                h.Log2MaxFrameNumMinus4 = uint32(e.SPS.Log2MaxFrameNumMinus4)
1✔
56
                h.Log2MaxPicOrderCntLsbMinus4 = uint32(e.SPS.Log2MaxPicOrderCntLsbMinus4)
1✔
57
                h.PicOrderCntType = uint8(e.SPS.PicOrderCntType)
1✔
58
        }
1✔
59
        if e.PPS != nil {
2✔
60
                h.PPSID = e.PPS.PicParameterSetID
1✔
61
                h.DeblockControlPresent = e.PPS.DeblockingFilterControlPresentFlag
1✔
62
                // slice_qp = pic_init_qp + slice_qp_delta. Solve for delta.
1✔
63
                h.QPDelta = int32(e.QP-26) - int32(e.PPS.PicInitQpMinus26)
1✔
64
        }
1✔
65
        return h
1✔
66
}
67

68
// plane returns the PlaneGrid to use for encoding, converting Grid+Colors if needed.
69
func (e *FrameEncoder) plane() (*yuv.PlaneGrid, error) {
1✔
70
        if e.Plane != nil {
2✔
71
                return e.Plane, nil
1✔
72
        }
1✔
73
        return yuv.GridToPlaneGrid(e.Grid, e.Colors)
1✔
74
}
75

76
// frameDimensions returns the frame width and height in pixels.
77
func (e *FrameEncoder) frameDimensions() (width, height int) {
1✔
78
        if e.Plane != nil {
2✔
79
                width = e.Plane.PixelWidth()
1✔
80
                height = e.Plane.PixelHeight()
1✔
81
        } else {
2✔
82
                width = e.Grid.Width * 16
1✔
83
                height = e.Grid.Height * 16
1✔
84
        }
1✔
85
        if e.Width > 0 {
2✔
86
                width = e.Width
1✔
87
        }
1✔
88
        if e.Height > 0 {
2✔
89
                height = e.Height
1✔
90
        }
1✔
91
        return width, height
1✔
92
}
93

94
// Encode produces an Annex-B bitstream containing SPS, PPS, and one IDR slice.
95
func (e *FrameEncoder) Encode() ([]byte, error) {
1✔
96
        var buf bytes.Buffer
1✔
97
        if err := e.EncodeSPSPPS(&buf); err != nil {
1✔
98
                return nil, err
×
99
        }
×
100
        slice, err := e.EncodeSlice(0)
1✔
101
        if err != nil {
1✔
102
                return nil, err
×
103
        }
×
104
        buf.Write(slice)
1✔
105
        return buf.Bytes(), nil
1✔
106
}
107

108
// EncodeSPSPPS writes SPS and PPS NALUs to buf (profile-aware).
109
func (e *FrameEncoder) EncodeSPSPPS(buf *bytes.Buffer) error {
1✔
110
        width, height := e.frameDimensions()
1✔
111

1✔
112
        level := ChooseLevel(width, height, e.FPS, e.Kbps, e.CABAC)
1✔
113
        if e.CABAC {
2✔
114
                spsRBSP := EncodeSPSMain(width, height, e.MaxNumRefFrames, level, e.ColorSpace, e.Range)
1✔
115
                if err := WriteNALU(buf, 7, 3, spsRBSP); err != nil {
1✔
116
                        return fmt.Errorf("write SPS: %w", err)
×
117
                }
×
118
                ppsRBSP := EncodePPSCABAC(e.DisableDeblock)
1✔
119
                if err := WriteNALU(buf, 8, 3, ppsRBSP); err != nil {
1✔
120
                        return fmt.Errorf("write PPS: %w", err)
×
121
                }
×
122
        } else {
1✔
123
                spsRBSP := EncodeSPS(width, height, e.MaxNumRefFrames, level, e.ColorSpace, e.Range)
1✔
124
                if err := WriteNALU(buf, 7, 3, spsRBSP); err != nil {
1✔
125
                        return fmt.Errorf("write SPS: %w", err)
×
126
                }
×
127
                ppsRBSP := EncodePPS(e.DisableDeblock)
1✔
128
                if err := WriteNALU(buf, 8, 3, ppsRBSP); err != nil {
1✔
129
                        return fmt.Errorf("write PPS: %w", err)
×
130
                }
×
131
        }
132
        return nil
1✔
133
}
134

135
// EncodeSlice encodes a single IDR slice NALU and returns it as Annex-B framed bytes.
136
// idrPicID is the idr_pic_id value (alternating 0/1 for consecutive IDR pictures).
137
func (e *FrameEncoder) EncodeSlice(idrPicID uint32) ([]byte, error) {
1✔
138
        if e.CABAC {
2✔
139
                return e.encodeSliceCABAC(idrPicID)
1✔
140
        }
1✔
141
        return e.encodeSliceCAVLC(idrPicID)
1✔
142
}
143

144
// EncodePSkipSlice encodes a P_Skip slice (all MBs skip, copying from reference).
145
// frameNum is the frame_num value for this slice.
146
// Returns an Annex-B framed non-IDR NALU (type=1, ref_idc=2).
147
func (e *FrameEncoder) EncodePSkipSlice(frameNum uint32) ([]byte, error) {
1✔
148
        width, height := e.frameDimensions()
1✔
149
        sps := &avc.SPS{
1✔
150
                Width:            uint(width),
1✔
151
                Height:           uint(height),
1✔
152
                PicOrderCntType:  0,
1✔
153
                FrameMbsOnlyFlag: true,
1✔
154
        }
1✔
155
        pps := &avc.PPS{
1✔
156
                DeblockingFilterControlPresentFlag: true,
1✔
157
                EntropyCodingModeFlag:              e.CABAC,
1✔
158
                PicInitQpMinus26:                   e.QP - 26,
1✔
159
        }
1✔
160
        // FrameEncoder controls both ends of its stream (IDR resets POC each
1✔
161
        // time), so picOrderCntLsb = 2*frame_num is correct here.
1✔
162
        return EncodePSkipSlice(sps, pps, frameNum, frameNum*2, e.DisableDeblock)
1✔
163
}
1✔
164

165
func (e *FrameEncoder) encodeSliceCAVLC(idrPicID uint32) ([]byte, error) {
1✔
166
        pg, err := e.plane()
1✔
167
        if err != nil {
1✔
168
                return nil, err
×
169
        }
×
170
        mbWidth := pg.MBWidth()
1✔
171
        mbHeight := pg.MBHeight()
1✔
172
        width := mbWidth * 16
1✔
173
        height := mbHeight * 16
1✔
174

1✔
175
        // Build slice (header + MB data) in a single BitWriter to maintain bit alignment
1✔
176
        sliceW := NewBitWriter()
1✔
177
        WriteSliceHeader(sliceW, e.idrSliceHeader(idrPicID))
1✔
178

1✔
179
        // Encode macroblocks
1✔
180
        // Track nC (number of non-zero coefficients) per 4x4 block for context
1✔
181
        // nC is computed from neighbors A (left) and B (above)
1✔
182
        nCLuma := make([][]int, mbHeight*4)
1✔
183
        for i := range nCLuma {
2✔
184
                nCLuma[i] = make([]int, mbWidth*4)
1✔
185
        }
1✔
186
        nCCb := make([][]int, mbHeight*2)
1✔
187
        for i := range nCCb {
2✔
188
                nCCb[i] = make([]int, mbWidth*2)
1✔
189
        }
1✔
190
        nCCr := make([][]int, mbHeight*2)
1✔
191
        for i := range nCCr {
2✔
192
                nCCr[i] = make([]int, mbWidth*2)
1✔
193
        }
1✔
194

195
        // Reconstructed pixel values (needed for DC prediction of neighbors)
196
        // Use flat slices with stride to minimize allocations.
197
        strideY := width
1✔
198
        strideC := width / 2
1✔
199
        reconY := make([]uint8, height*strideY)
1✔
200
        reconCb := make([]uint8, (height/2)*strideC)
1✔
201
        reconCr := make([]uint8, (height/2)*strideC)
1✔
202

1✔
203
        for mbY := range mbHeight {
2✔
204
                for mbX := range mbWidth {
2✔
205
                        lumaVals := pg.MBLumaValues(mbX, mbY)
1✔
206
                        cbVals, crVals := pg.MBChromaSub(mbX, mbY)
1✔
207

1✔
208
                        err := e.encodeMBPlane(sliceW, mbX, mbY, lumaVals, cbVals, crVals,
1✔
209
                                nCLuma, nCCb, nCCr, reconY, strideY, reconCb, reconCr, strideC)
1✔
210
                        if err != nil {
1✔
211
                                return nil, fmt.Errorf("encode MB (%d,%d): %w", mbX, mbY, err)
×
212
                        }
×
213
                }
214
        }
215

216
        // RBSP trailing bits
217
        sliceW.WriteBit(1) // rbsp_stop_one_bit
1✔
218
        sliceW.AlignToByte()
1✔
219

1✔
220
        var buf bytes.Buffer
1✔
221
        sliceRBSP := sliceW.Bytes()
1✔
222
        if err := WriteNALU(&buf, 5, 3, sliceRBSP); err != nil {
1✔
223
                return nil, fmt.Errorf("write IDR: %w", err)
×
224
        }
×
225

226
        return buf.Bytes(), nil
1✔
227
}
228

229
// encMBState tracks encoder-side MB state for CABAC context derivation.
230
type encMBState struct {
231
        mbType              int
232
        intraChromaPredMode int
233
        cbpChroma           int
234
        codedBlockFlag      [6][16]uint8
235
}
236

237
func (e *FrameEncoder) encodeSliceCABAC(idrPicID uint32) ([]byte, error) {
1✔
238
        pg, err := e.plane()
1✔
239
        if err != nil {
1✔
240
                return nil, err
×
241
        }
×
242
        mbWidth := pg.MBWidth()
1✔
243
        mbHeight := pg.MBHeight()
1✔
244
        width := mbWidth * 16
1✔
245
        height := mbHeight * 16
1✔
246

1✔
247
        // Build slice header with CABAC alignment
1✔
248
        headerW := NewBitWriter()
1✔
249
        WriteSliceHeaderCABAC(headerW, e.idrSliceHeader(idrPicID))
1✔
250
        headerBytes := headerW.Bytes()
1✔
251

1✔
252
        // Initialize CABAC encoder and context models
1✔
253
        enc := cabac.NewEncoder()
1✔
254
        models := context.InitModels(e.QP, 2, 0) // sliceType=2 (I-slice)
1✔
255
        ctx := models[:]
1✔
256

1✔
257
        // MB state for neighbor context derivation
1✔
258
        mbStates := make([]encMBState, mbWidth*mbHeight)
1✔
259

1✔
260
        // Reconstructed pixel values (needed for DC prediction of neighbors)
1✔
261
        // Use flat slices with stride to minimize allocations.
1✔
262
        strideY := width
1✔
263
        strideC := width / 2
1✔
264
        reconY := make([]uint8, height*strideY)
1✔
265
        reconCb := make([]uint8, (height/2)*strideC)
1✔
266
        reconCr := make([]uint8, (height/2)*strideC)
1✔
267

1✔
268
        for mbY := range mbHeight {
2✔
269
                for mbX := range mbWidth {
2✔
270
                        lumaVals := pg.MBLumaValues(mbX, mbY)
1✔
271
                        cbVals, crVals := pg.MBChromaSub(mbX, mbY)
1✔
272

1✔
273
                        mbIdx := mbY*mbWidth + mbX
1✔
274
                        err := e.encodeMBCABACPlane(enc, ctx, mbStates, mbIdx, mbX, mbY,
1✔
275
                                lumaVals, cbVals, crVals,
1✔
276
                                mbWidth, mbHeight, reconY, strideY, reconCb, reconCr, strideC)
1✔
277
                        if err != nil {
1✔
278
                                return nil, fmt.Errorf("encode MB (%d,%d): %w", mbX, mbY, err)
×
279
                        }
×
280

281
                        // end_of_slice: terminate(1) for last MB, terminate(0) otherwise
282
                        isLast := mbY == mbHeight-1 && mbX == mbWidth-1
1✔
283
                        if isLast {
2✔
284
                                enc.EncodeTerminate(1)
1✔
285
                        } else {
2✔
286
                                enc.EncodeTerminate(0)
1✔
287
                        }
1✔
288
                }
289
        }
290

291
        cabacBytes := enc.Flush()
1✔
292

1✔
293
        // Concatenate header + CABAC data as RBSP
1✔
294
        sliceRBSP := make([]byte, 0, len(headerBytes)+len(cabacBytes))
1✔
295
        sliceRBSP = append(sliceRBSP, headerBytes...)
1✔
296
        sliceRBSP = append(sliceRBSP, cabacBytes...)
1✔
297

1✔
298
        var buf bytes.Buffer
1✔
299
        if err := WriteNALU(&buf, 5, 3, sliceRBSP); err != nil {
1✔
300
                return nil, fmt.Errorf("write IDR: %w", err)
×
301
        }
×
302

303
        return buf.Bytes(), nil
1✔
304
}
305

306
// deriveDCCBFCtx derives the coded_block_flag context for Intra16x16DC.
307
// condTermFlagA/B default to 1 when neighbor is not available.
308
func deriveDCCBFCtx(left, top *encMBState) int {
1✔
309
        condA := 1
1✔
310
        condB := 1
1✔
311
        if left != nil {
2✔
312
                condA = int(left.codedBlockFlag[encCtxBlockCatIntra16x16DC][0])
1✔
313
        }
1✔
314
        if top != nil {
2✔
315
                condB = int(top.codedBlockFlag[encCtxBlockCatIntra16x16DC][0])
1✔
316
        }
1✔
317
        return condA + 2*condB
1✔
318
}
319

320
// deriveChromaDCCBFCtx derives the coded_block_flag context for ChromaDC.
321
// iCbCr: 0 for Cb, 1 for Cr.
322
func deriveChromaDCCBFCtx(left, top *encMBState, iCbCr int) int {
1✔
323
        condA := 1
1✔
324
        condB := 1
1✔
325
        if left != nil {
2✔
326
                condA = int(left.codedBlockFlag[encCtxBlockCatChromaDC][iCbCr])
1✔
327
        }
1✔
328
        if top != nil {
2✔
329
                condB = int(top.codedBlockFlag[encCtxBlockCatChromaDC][iCbCr])
1✔
330
        }
1✔
331
        return condA + 2*condB
1✔
332
}
333

334
func computeLumaDCPred(reconY []uint8, strideY, mbX, mbY int) uint8 {
1✔
335
        hasTop := mbY > 0
1✔
336
        hasLeft := mbX > 0
1✔
337

1✔
338
        if !hasTop && !hasLeft {
2✔
339
                return 128
1✔
340
        }
1✔
341

342
        sum := 0
1✔
343
        count := 0
1✔
344

1✔
345
        if hasTop {
2✔
346
                off := (mbY*16-1)*strideY + mbX*16
1✔
347
                for x := range 16 {
2✔
348
                        sum += int(reconY[off+x])
1✔
349
                }
1✔
350
                count += 16
1✔
351
        }
352

353
        if hasLeft {
2✔
354
                off := mbY*16*strideY + mbX*16 - 1
1✔
355
                for y := range 16 {
2✔
356
                        sum += int(reconY[off+y*strideY])
1✔
357
                }
1✔
358
                count += 16
1✔
359
        }
360

361
        if count == 32 {
2✔
362
                return uint8((sum + 16) >> 5)
1✔
363
        }
1✔
364
        return uint8((sum + 8) >> 4)
1✔
365
}
366

367
// lumaPredict16x16 computes the per-pixel I_16x16 prediction array for the given mode.
368
// This matches the decoder's prediction: V mode uses the full top row, H mode uses
369
// the full left column, DC mode uses a uniform value.
370
func lumaPredict16x16(reconY []uint8, strideY, mbX, mbY, mode int) [256]uint8 {
1✔
371
        var pred [256]uint8
1✔
372
        switch mode {
1✔
373
        case 0: // Vertical — each column gets top row pixel
1✔
374
                off := (mbY*16-1)*strideY + mbX*16
1✔
375
                for x := range 16 {
2✔
376
                        topVal := reconY[off+x]
1✔
377
                        for y := range 16 {
2✔
378
                                pred[y*16+x] = topVal
1✔
379
                        }
1✔
380
                }
381
        case 1: // Horizontal — each row gets left column pixel
1✔
382
                for y := range 16 {
2✔
383
                        leftVal := reconY[(mbY*16+y)*strideY+mbX*16-1]
1✔
384
                        for x := range 16 {
2✔
385
                                pred[y*16+x] = leftVal
1✔
386
                        }
1✔
387
                }
388
        case 2: // DC — uniform value
1✔
389
                dcVal := computeLumaDCPred(reconY, strideY, mbX, mbY)
1✔
390
                for i := range pred {
2✔
391
                        pred[i] = dcVal
1✔
392
                }
1✔
393
        }
394
        return pred
1✔
395
}
396

397
// chromaPredict8x8 computes the per-pixel chroma prediction array for the given mode.
398
// This matches the decoder's chroma prediction: DC gives per-sub-block averages,
399
// V copies the top row, H copies the left column.
400
func chromaPredict8x8(recon []uint8, strideC, mbX, mbY, mode int) [64]uint8 {
1✔
401
        var pred [64]uint8
1✔
402
        switch mode {
1✔
403
        case 0: // DC — per-sub-block DC prediction
1✔
404
                dcPreds := computeChromaDCPreds4x4(recon, strideC, mbX, mbY)
1✔
405
                for blk := range 4 {
2✔
406
                        x0 := (blk % 2) * 4
1✔
407
                        y0 := (blk / 2) * 4
1✔
408
                        for y := range 4 {
2✔
409
                                for x := range 4 {
2✔
410
                                        pred[(y0+y)*8+x0+x] = dcPreds[blk]
1✔
411
                                }
1✔
412
                        }
413
                }
414
        case 1: // Horizontal — each row gets left column pixel
1✔
415
                for y := range 8 {
2✔
416
                        leftVal := recon[(mbY*8+y)*strideC+mbX*8-1]
1✔
417
                        for x := range 8 {
2✔
418
                                pred[y*8+x] = leftVal
1✔
419
                        }
1✔
420
                }
421
        case 2: // Vertical — each column gets top row pixel
1✔
422
                off := (mbY*8-1)*strideC + mbX*8
1✔
423
                for x := range 8 {
2✔
424
                        topVal := recon[off+x]
1✔
425
                        for y := range 8 {
2✔
426
                                pred[y*8+x] = topVal
1✔
427
                        }
1✔
428
                }
429
        }
430
        return pred
1✔
431
}
432

433
func absInt(v int) int {
1✔
434
        if v < 0 {
2✔
435
                return -v
1✔
436
        }
1✔
437
        return v
1✔
438
}
439

440
// computeChromaDCPreds4x4 computes per-sub-block chroma DC predictions,
441
// matching the decoder's predictChromaDC8x8 (spec section 8.3.4.1).
442
// Returns [4]uint8 for sub-blocks: TL(0), TR(1), BL(2), BR(3).
443
func computeChromaDCPreds4x4(recon []uint8, strideC, mbX, mbY int) [4]uint8 {
1✔
444
        hasTop := mbY > 0
1✔
445
        hasLeft := mbX > 0
1✔
446

1✔
447
        var top [8]int
1✔
448
        var left [8]int
1✔
449
        if hasTop {
2✔
450
                off := (mbY*8-1)*strideC + mbX*8
1✔
451
                for x := range 8 {
2✔
452
                        top[x] = int(recon[off+x])
1✔
453
                }
1✔
454
        }
455
        if hasLeft {
2✔
456
                off := mbY*8*strideC + mbX*8 - 1
1✔
457
                for y := range 8 {
2✔
458
                        left[y] = int(recon[off+y*strideC])
1✔
459
                }
1✔
460
        }
461

462
        var preds [4]uint8
1✔
463
        for blk := range 4 {
2✔
464
                x0 := (blk % 2) * 4
1✔
465
                y0 := (blk / 2) * 4
1✔
466

1✔
467
                sum := 0
1✔
468
                count := 0
1✔
469

1✔
470
                isTopRow := blk < 2
1✔
471
                isLeftCol := blk%2 == 0
1✔
472

1✔
473
                useTop := false
1✔
474
                useLeft := false
1✔
475

1✔
476
                switch {
1✔
477
                case isTopRow && isLeftCol: // TL: use both if available
1✔
478
                        useTop = hasTop
1✔
479
                        useLeft = hasLeft
1✔
480
                case isTopRow && !isLeftCol: // TR: prefer top, fallback to left
1✔
481
                        if hasTop {
2✔
482
                                useTop = true
1✔
483
                        } else if hasLeft {
3✔
484
                                useLeft = true
1✔
485
                        }
1✔
486
                case !isTopRow && isLeftCol: // BL: prefer left, fallback to top
1✔
487
                        if hasLeft {
2✔
488
                                useLeft = true
1✔
489
                        } else if hasTop {
3✔
490
                                useTop = true
1✔
491
                        }
1✔
492
                default: // BR: use both if available
1✔
493
                        useTop = hasTop
1✔
494
                        useLeft = hasLeft
1✔
495
                }
496

497
                if useTop {
2✔
498
                        for x := x0; x < x0+4; x++ {
2✔
499
                                sum += top[x]
1✔
500
                                count++
1✔
501
                        }
1✔
502
                }
503
                if useLeft {
2✔
504
                        for y := y0; y < y0+4; y++ {
2✔
505
                                sum += left[y]
1✔
506
                                count++
1✔
507
                        }
1✔
508
                }
509

510
                if count > 0 {
2✔
511
                        preds[blk] = uint8((sum + count/2) / count)
1✔
512
                } else {
2✔
513
                        preds[blk] = 128
1✔
514
                }
1✔
515
        }
516
        return preds
1✔
517
}
518

519
func computeNC4x4(nCLuma [][]int, blkX, blkY int) int {
1✔
520
        hasA := blkX > 0
1✔
521
        hasB := blkY > 0
1✔
522

1✔
523
        nA, nB := 0, 0
1✔
524
        if hasA {
2✔
525
                nA = nCLuma[blkY][blkX-1]
1✔
526
        }
1✔
527
        if hasB {
2✔
528
                nB = nCLuma[blkY-1][blkX]
1✔
529
        }
1✔
530

531
        if hasA && hasB {
2✔
532
                return (nA + nB + 1) / 2
1✔
533
        }
1✔
534
        if hasA {
2✔
535
                return nA
1✔
536
        }
1✔
537
        if hasB {
2✔
538
                return nB
1✔
539
        }
1✔
540
        return 0
1✔
541
}
542

543
func clipU8(v int) uint8 {
1✔
544
        if v < 0 {
1✔
545
                return 0
×
546
        }
×
547
        if v > 255 {
1✔
548
                return 255
×
549
        }
×
550
        return uint8(v)
1✔
551
}
552

553
// Inlined inverse transforms to avoid import cycle with transform package
554
func dequantDC4x4(coeffs [16]int32, qp int, weightScaleDC int32) [16]int32 {
1✔
555
        var result [16]int32
1✔
556
        qpPer := qp / 6
1✔
557
        qpRem := qp % 6
1✔
558
        levelScale := levelScale4x4[qpRem][0] * weightScaleDC
1✔
559
        if qpPer >= 6 {
1✔
560
                for i := range 16 {
×
561
                        result[i] = coeffs[i] * levelScale << uint(qpPer-6)
×
562
                }
×
563
        } else {
1✔
564
                for i := range 16 {
2✔
565
                        result[i] = (coeffs[i]*levelScale + (1 << uint(5-qpPer))) >> uint(6-qpPer)
1✔
566
                }
1✔
567
        }
568
        return result
1✔
569
}
570

571
func dequantChromaDC2x2(coeffs [4]int32, qpc int, weightScaleDC int32) [4]int32 {
1✔
572
        var result [4]int32
1✔
573
        qpPer := qpc / 6
1✔
574
        qpRem := qpc % 6
1✔
575
        levelScale := levelScale4x4[qpRem][0] * weightScaleDC
1✔
576
        if qpPer >= 5 {
1✔
577
                for i := range 4 {
×
578
                        result[i] = coeffs[i] * levelScale << uint(qpPer-5)
×
579
                }
×
580
        } else {
1✔
581
                for i := range 4 {
2✔
582
                        result[i] = (coeffs[i] * levelScale) >> uint(5-qpPer)
1✔
583
                }
1✔
584
        }
585
        return result
1✔
586
}
587

588
func inverseHadamard4x4(coeffs [16]int32) [16]int32 {
1✔
589
        var temp [16]int32
1✔
590
        for i := range 4 {
2✔
591
                s0, s1, s2, s3 := coeffs[i*4], coeffs[i*4+1], coeffs[i*4+2], coeffs[i*4+3]
1✔
592
                temp[i*4+0] = s0 + s1 + s2 + s3
1✔
593
                temp[i*4+1] = s0 + s1 - s2 - s3
1✔
594
                temp[i*4+2] = s0 - s1 - s2 + s3
1✔
595
                temp[i*4+3] = s0 - s1 + s2 - s3
1✔
596
        }
1✔
597
        var result [16]int32
1✔
598
        for j := range 4 {
2✔
599
                f0, f1, f2, f3 := temp[j], temp[4+j], temp[8+j], temp[12+j]
1✔
600
                result[j] = f0 + f1 + f2 + f3
1✔
601
                result[4+j] = f0 + f1 - f2 - f3
1✔
602
                result[8+j] = f0 - f1 - f2 + f3
1✔
603
                result[12+j] = f0 - f1 + f2 - f3
1✔
604
        }
1✔
605
        return result
1✔
606
}
607

608
func inverseHadamard2x2(coeffs [4]int32) [4]int32 {
1✔
609
        return [4]int32{
1✔
610
                coeffs[0] + coeffs[1] + coeffs[2] + coeffs[3],
1✔
611
                coeffs[0] - coeffs[1] + coeffs[2] - coeffs[3],
1✔
612
                coeffs[0] + coeffs[1] - coeffs[2] - coeffs[3],
1✔
613
                coeffs[0] - coeffs[1] - coeffs[2] + coeffs[3],
1✔
614
        }
1✔
615
}
1✔
616

617
var levelScale4x4 = [6][3]int32{
618
        {10, 13, 16},
619
        {11, 14, 18},
620
        {13, 16, 20},
621
        {14, 18, 23},
622
        {16, 20, 25},
623
        {18, 23, 29},
624
}
625

626
// inverseRasterX4x4 maps 4x4 block index (0-15) to x position within MB.
627
var inverseRasterX4x4 = [16]int{
628
        0, 4, 0, 4, 8, 12, 8, 12,
629
        0, 4, 0, 4, 8, 12, 8, 12,
630
}
631

632
// inverseRasterY4x4 maps 4x4 block index (0-15) to y position within MB.
633
var inverseRasterY4x4 = [16]int{
634
        0, 0, 4, 4, 0, 0, 4, 4,
635
        8, 8, 12, 12, 8, 8, 12, 12,
636
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc