From f6f2f02e5aa07846dcd78218389d962f2c6fc373 Mon Sep 17 00:00:00 2001
From: sam <sam@cinelerra-gg.org>
Date: Wed, 14 Oct 2026 21:35:55 +0200
Subject: [PATCH] MPEG 4:2:2: encode every macroblock row on any processor
 count

The 4:2:2 encoder splits each picture into row bands, one per processor,
for its slice, transform, inverse transform and motion threads. Each band
was the rounded share mb_height / processors. When the bands added up to
fewer rows than the picture had, the bottom rows were never encoded:
on 16 processors 720x576 (36 rows, bands of 2) lost rows 32 to 35 in
every picture, and ffmpeg reported "overread" and "MVs not available".
Whether a size broke depended on the processor count, which is why
720x480 and 720x512 came out clean on the same machine.

Split the rows evenly instead, so the last band ends on the last row.
On 16 processors, 20 sizes from 176x144 to 1920x1088 now decode without
a message. 720x576 against the source: PSNR 16.9 dB before, 40.7 dB
after. The upstream AppImage of 2026-02-02 writes the same broken file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
 cinelerra-5.1/mpeg2enc/motion.c   |  9 +++------
 cinelerra-5.1/mpeg2enc/putpic.c   | 11 +++++------
 cinelerra-5.1/mpeg2enc/transfrm.c | 18 ++++++------------
 3 files changed, 14 insertions(+), 24 deletions(-)

diff --git a/cinelerra-5.1/mpeg2enc/motion.c b/cinelerra-5.1/mpeg2enc/motion.c
index 995597af..4cc72de4 100644
--- a/cinelerra-5.1/mpeg2enc/motion.c
+++ b/cinelerra-5.1/mpeg2enc/motion.c
@@ -3048,8 +3048,6 @@ void motion_estimation(pict_data_s *picture,
 void start_motion_engines()
 {
 	int i;
-	int rows_per_processor = (int)((float)height2 / 16 / processors + 0.5);
-	int current_row = 0;
 	pthread_attr_t  attr;
 	pthread_mutexattr_t mutex_attr;
 
@@ -3058,10 +3056,9 @@ void start_motion_engines()
 	motion_engines = calloc(1, sizeof(motion_engine_t) * processors);
 	for(i = 0; i < processors; i++)
 	{
-		motion_engines[i].start_row = current_row * 16;
-		current_row += rows_per_processor;
-		if(current_row > height2 / 16) current_row = height2 / 16;
-		motion_engines[i].end_row = current_row * 16;
+// Even split in macroblock rows, see start_slice_engines()
+		motion_engines[i].start_row = height2 / 16 * i / processors * 16;
+		motion_engines[i].end_row = height2 / 16 * (i + 1) / processors * 16;
 		pthread_mutex_init(&(motion_engines[i].input_lock), &mutex_attr);
 		pthread_mutex_lock(&(motion_engines[i].input_lock));
 		pthread_mutex_init(&(motion_engines[i].output_lock), &mutex_attr);
diff --git a/cinelerra-5.1/mpeg2enc/putpic.c b/cinelerra-5.1/mpeg2enc/putpic.c
index 42ca1abf..43b7789c 100644
--- a/cinelerra-5.1/mpeg2enc/putpic.c
+++ b/cinelerra-5.1/mpeg2enc/putpic.c
@@ -462,8 +462,6 @@ void putpict(pict_data_s *picture)
 void start_slice_engines()
 {
 	int i;
-	int rows_per_processor = (int)((float)mb_height2 / processors + 0.5);
-	int current_row = 0;
 	pthread_attr_t attr;
 	pthread_mutexattr_t mutex_attr;
 
@@ -474,10 +472,11 @@ void start_slice_engines()
 	slice_engines = calloc(1, sizeof(slice_engine_t) * processors);
 	for(i = 0; i < processors; i++)
 	{
-		slice_engines[i].start_row = current_row;
-		current_row += rows_per_processor;
-		if(current_row > mb_height2) current_row = mb_height2;
-		slice_engines[i].end_row = current_row;
+// Split the rows evenly, the last engine ends on the last row.  A rounded
+// share per engine left the bottom rows out whenever processors * share
+// fell short of mb_height2 (36 rows on 16 processors encoded 32).
+		slice_engines[i].start_row = mb_height2 * i / processors;
+		slice_engines[i].end_row = mb_height2 * (i + 1) / processors;
 		
 		pthread_mutex_init(&(slice_engines[i].input_lock), &mutex_attr);
 		pthread_mutex_lock(&(slice_engines[i].input_lock));
diff --git a/cinelerra-5.1/mpeg2enc/transfrm.c b/cinelerra-5.1/mpeg2enc/transfrm.c
index fc4141b3..b4bdff90 100644
--- a/cinelerra-5.1/mpeg2enc/transfrm.c
+++ b/cinelerra-5.1/mpeg2enc/transfrm.c
@@ -266,8 +266,6 @@ void transform(pict_data_s *picture,
 void start_transform_engines()
 {
 	int i;
-	int rows_per_processor = (int)((float)height2 / 16 / processors + 0.5);
-	int current_row = 0;
 	pthread_attr_t  attr;
 	pthread_mutexattr_t mutex_attr;
 
@@ -276,10 +274,9 @@ void start_transform_engines()
 	transform_engines = calloc(1, sizeof(transform_engine_t) * processors);
 	for(i = 0; i < processors; i++)
 	{
-		transform_engines[i].start_row = current_row * 16;
-		current_row += rows_per_processor;
-		if(current_row > height2 / 16) current_row = height2 / 16;
-		transform_engines[i].end_row = current_row * 16;
+// Even split in macroblock rows, see start_slice_engines()
+		transform_engines[i].start_row = height2 / 16 * i / processors * 16;
+		transform_engines[i].end_row = height2 / 16 * (i + 1) / processors * 16;
 		pthread_mutex_init(&(transform_engines[i].input_lock), &mutex_attr);
 		pthread_mutex_lock(&(transform_engines[i].input_lock));
 		pthread_mutex_init(&(transform_engines[i].output_lock), &mutex_attr);
@@ -422,8 +419,6 @@ void itransform(pict_data_s *picture,
 void start_itransform_engines()
 {
 	int i;
-	int rows_per_processor = (int)((float)height2 / 16 / processors + 0.5);
-	int current_row = 0;
 	pthread_attr_t  attr;
 	pthread_mutexattr_t mutex_attr;
 
@@ -432,10 +427,9 @@ void start_itransform_engines()
 	itransform_engines = calloc(1, sizeof(transform_engine_t) * processors);
 	for(i = 0; i < processors; i++)
 	{
-		itransform_engines[i].start_row = current_row * 16;
-		current_row += rows_per_processor;
-		if(current_row > height2 / 16) current_row = height2 / 16;
-		itransform_engines[i].end_row = current_row * 16;
+// Even split in macroblock rows, see start_slice_engines()
+		itransform_engines[i].start_row = height2 / 16 * i / processors * 16;
+		itransform_engines[i].end_row = height2 / 16 * (i + 1) / processors * 16;
 		pthread_mutex_init(&(itransform_engines[i].input_lock), &mutex_attr);
 		pthread_mutex_lock(&(itransform_engines[i].input_lock));
 		pthread_mutex_init(&(itransform_engines[i].output_lock), &mutex_attr);
-- 
2.43.0

