4K H.265 → H.264 Video Server

Part 2 — Reconstructing 4K Video on a Radeon HD 6570 and Athlon 64 X2

A continuation of Part 1 — Making 4K Video More Compatible.

In Part 1, I described the idea of a dedicated video server: take modern 4K H.265 video, convert it to H.264, and make it usable by older devices. The point was not to improve the original video. It was to bridge generations of hardware without having to replace every player and computer along the way.

That leaves another problem.

H.264 is compatible with older hardware, but 4K H.264 is not necessarily easy for that hardware to play.

A graphics card may have an H.264 decoder and still be limited by the picture sizes, profiles, throughput or memory arrangements its video engine was designed to handle. In other words, changing the codec does not automatically remove the resolution problem.

So I decided to investigate a particularly old combination: an AMD Radeon HD 6570 and an Athlon 64 X2 4200+, a dual-core processor running at 2.2 GHz. The computer ran Ubuntu Server without a desktop environment. Its display was connected through VGA at 1024 × 768.

The question was no longer simply whether the server could convert H.265 to H.264. It was:

Could the client reconstruct a complete 3840 × 2160 picture by dividing the work between hardware and software decoders?

The source material was a 3840 × 2160 HEVC Main10 video, converted upstream into H.264 test streams. The older client was not asked to decode H.265 or encode H.264 itself. The final reconstruction trials used a 45-second excerpt (06:45–07:30) at 23.976 fps, containing 1,079 frames. Although the graphics pipeline worked with a complete 4K picture internally, it scaled that image to 1024 × 576 within the 1024 × 768 VGA output. That distinction matters: we investigated full-resolution reconstruction and real-time presentation, not native 4K display output.

Measuring capability rather than reading a specification sheet

We ran three fundamentally different kinds of experiment. Isolated decoding measured how quickly a single H.264 stream could be decoded when no attempt was made to display it. Concurrent decoding ran multiple streams together and reported the rate of each stream, not their combined FPS. Finally, reconstruction tests decoded tiles, matched their presentation timestamps, assembled the image and, later, displayed it.

Those numbers cannot be substituted for one another. A 60 fps decode-to-null test does not guarantee a 60 fps compositor. Similarly, a video that averages 24 fps over 45 seconds may still have individual frames that arrive late. Keeping these distinctions visible became as important as optimizing the code.

Characterizing the Radeon HD 6570

Before attempting 4K reconstruction, I wanted to understand what the HD 6570 could actually decode. The card uses AMD’s Turks GPU and UVD video engine. Under Ubuntu Server, FFmpeg accessed it through the r600 VA-API driver. The tests ran without a desktop environment so that we could measure decoding without confusing it with display performance.

An H.264 hardware decoder has several different limits. It may accept one picture size but reject another. It may decode a single stream rapidly, yet become too slow when several streams compete for the same video engine. And an easy H.264 sequence may be decoded much faster than a demanding one at the same resolution. We investigated those questions separately.

The first multi-stream question: how many 1080p videos?

We began the concurrent-decoding experiments with familiar 1920 × 1080 H.264 video, using 30 fps per stream as the performance target. One stream was easy. Two still worked. At three, the card continued to decode every stream, but its throughput dropped below the target.

Simultaneous 1920 × 1080 streamsDecoding speed, per streamMeets 30 fps?
165 fpsYes
236–37 fpsYes
324 fpsNo
418–19 fpsNo

These first trials used 150 frames per stream. “Three streams failed” did not mean a decoder error: three streams completed successfully, but they were no longer fast enough to sustain the chosen 30 fps target. Multiplying the per-stream numbers by the stream count gives an aggregate of roughly 65–75 decoded 1080p frames per second across these trials. This suggested that the streams were competing for a relatively fixed decoding capacity, rather than receiving independent full-speed video engines.

There is a subtle point about the word failed. We deliberately chose 30 fps as the pass criterion in this concurrency exploration, even though our later movie excerpt was 23.976 fps. Three 1080p streams at approximately 24 fps were not a robust 30 fps solution, and they left essentially no margin for the 23.976 fps source. That is why the next experiment was not to add another decoder, but to reduce the amount of work per stream.

That immediately led to another question: if we made each stream smaller, could the HD 6570 handle three or even four simultaneously at 30 fps?

Smaller pictures, more simultaneous streams

We repeated the concurrent-decoding tests at three smaller resolutions, running 600 frames per stream. The results were revealing:

Resolution of each stream1 stream2 streams3 streams4 streams
1920 × 10806536–372418–19
1280 × 1152102533526
1280 × 1024113593930–31
1280 × 720156825541

All values are measured frames per second per stream, not the sum across streams. The 1080p row comes from the earlier 150-frame tests; the smaller-resolution rows are 600-frame tests, so the figures describe distinct experiments.

The Radeon HD 6570 could decode two 1080p H.264 streams above 30 fps, but reducing the resolution allowed three or even four simultaneous streams to reach that target.

The contrast with the initial 1080p result is significant. Three 1280 × 1152 streams ran at 35 fps each, while four 1280 × 1024 streams just reached 30–31 fps each. With four 1280 × 720 streams, each stream reached 41 fps. We did not test more than four streams in this particular set, so that is a demonstrated result, not a claim about the absolute maximum number of simultaneous streams.

The number of simultaneous H.264 streams meeting the 30 fps requirement depended strongly on their individual resolutions

These numbers changed the direction of the investigation. Instead of treating 1080p as an indivisible unit, we could select tile dimensions that gave the GPU enough throughput for several simultaneous streams. A 1280 × 720 stream contains fewer than half as many pixels as 1920 × 1080; the difference was large enough that four 720p streams each exceeded 30 fps.

At the same time, the test showed why a blanket statement such as “the HD 6570 can decode four videos” would be misleading. How many streams? depended on how large and demanding each stream was. The next step was to find dimensions that suited the decoder, then decide how those pieces could be arranged into a larger picture.

Exploring unusual dimensions and single-stream boundaries

We also characterized single-stream speed at a wider set of resolutions. The measurements below come from a separate resolution sweep; differences between its values and the concurrency tests reflect different test runs and workloads.

H.264 stream resolutionMeasured throughput
1280 × 720132 fps
1280 × 800121 fps
1280 × 900109 fps
1280 × 960104 fps
1280 × 102499 fps
1280 × 108093 fps
1440 × 90098 fps
1440 × 108084 fps
1600 × 90089 fps
1600 × 100082 fps
1600 × 108077 fps
1920 × 108066 fps
2048 × 108068 fps
2048 × 115259 fps
Testing different image dimensions revealed how the HD 6570’s decoding throughput changed with resolution, including unusual frame sizes.

Within this particular sweep, 1280 × 720 reached 132 fps, while even 2048 × 1152 maintained 59 fps. The highest single-stream figure across the separately recorded tests was 156 fps at 1280 × 720 in the concurrency batch. The two 720p results are not a contradiction: the test content and procedures were different, so the correct interpretation is that they are measurements from separate runs, not a universal maximum.

A separate FFmpeg/null-output run at 2048 × 1152 completed 1,079 frames in 19.98 seconds, or about 54 fps. We also tested unconventional portrait-oriented dimensions, including 1080 × 1920 and 720 × 1280, which this VA-API decoding path rejected. That does not, on its own, prove every imaginable hardware limit, but it demonstrates that pixel count, accepted dimensions, and decoding throughput are three distinct questions.

Bitrate changed the result as well

So far, the experiments established that lower resolution increased the number of simultaneous streams. But could we predict decoding performance from pixel count alone? To answer that, we compared different H.264 encodings of the same real-world 1920 × 1080, 23.976 fps video (video_decode_test2.mp4). Each version contained approximately 3,207 frames. We tested an approximately 1.76 Mbps original and re-encodes at approximately 19.94 and 29.01 Mbps. The latter was the actual bitrate reached by encodes targeting higher rates; it was not a 40 or 60 Mbps result.

Actual bitrateHD 6570, GPU decodingAthlon 64 X2, CPU decoding
1.76 Mbps73 fps93 fps
19.94 Mbps60 fps47 fps
29.01 Mbps53 fps39 fps

These were unrestricted single-stream FFmpeg decode-to-null measurements. They are not display FPS, and the original and re-encoded files were not necessarily equally demanding H.264 bitstreams apart from bitrate. The encoder’s motion prediction, reference-frame decisions and other coding choices also matter. Nevertheless, for these actual files, the Radeon fell from 73 to 53 fps, while the Athlon fell from 93 to 39 fps. At the lowest rate, the CPU was faster in this particular test; at the highest rate, the GPU was faster. The Athlon was much more sensitive to the demanding encodes.

Comparing hardware and software decoding at different H.264 bitrates helped establish the practical roles of the Radeon HD 6570 and Athlon 64 X2.

We also ran two GPU-decoded streams and two CPU-decoded streams concurrently. This was still a decode-capacity test, not yet a four-tile composited video. Each of the four decoders processed its own 1080p stream of the tested bitrate:

Bitrate per 1080p streamEach GPU streamEach CPU stream23.976 fps source rate
1.76 Mbps~36 fps43–44 fpsAll four above target
19.94 Mbps~30 fps~24 fpsBorderline on CPU
29.01 Mbps~27 fps~19 fpsCPU streams below target

These mixed trials were important for the next stage. They showed that the old Athlon was not incapable of participating in a multi-stream reconstruction; it could perform useful decoding work, provided we controlled the number of pixels assigned to it and the difficulty of the H.264 stream. They also warned us not to rely on a low-bitrate success as proof that every 1080p file would behave the same way.

Combined hardware and software decoding experiments revealed the effect of increasing bitrate on multi-stream throughput.

Proving that two GPU streams could become one picture

Decoding several streams was only the first half of the idea. To use those streams as tiles of a larger image, we had to combine them without wasting time copying complete frames between the GPU and system memory.

We split a 2048 × 1152 picture into two 2048 × 576 H.264 streams, each approximately 10 Mbps. The Radeon decoded both at around 57 fps per stream. The new GPU compositor then reconstructed the image using textures and a shader. It processed 1,079 synchronized frame pairs in 21.663 seconds — 49.81 reconstructed frames per second — with zero timestamp mismatches and no full-frame CPU readback.

Two independently decoded H.264 streams were reconstructed into a single 2048 × 1152 image using GPU composition.

A separate earlier FFmpeg test also completed a 2048 × 1152 GPU-only decode/reconstruction workload at about 54 fps, but the 49.81 fps figure above is the more informative result for the dedicated two-tile GPU compositor: it includes synchronized tile handling and composition, not just unrestricted decoder output.

This was the proof of concept that connected the earlier benchmarks to the larger project. The HD 6570 did not need to decode one oversized frame. It could decode supported pieces and let a shader reconstruct the picture. We were now ready to go beyond GPU-only stitching and ask how much of the remaining image area the Athlon could handle in software.

Then, test what the Athlon could contribute

Having measured the Athlon’s software decoder as well as the Radeon, we could make a more sensible division of the image. The Athlon 64 X2 4200+ is a dual-core, 2.2 GHz processor from an era long before software 4K H.264 playback was a reasonable expectation. We were not going to give it the entire 4K picture. Instead, the GPU would decode two upper tiles while the CPU decoded a carefully sized lower region. The bitrate experiments had already shown why the CPU’s assigned workload had to remain manageable. What mattered now was whether all three tiles could arrive in time to reconstruct each source frame.

We first worked at 2880 × 1620, a step beyond conventional full HD but not yet the full 4K workload. An early FFmpeg-based hybrid reconstruction finished 1,079 frames in approximately 41.67 seconds (about 25.9 fps). That was just above the 23.976 fps movie rate, but offered little room for the other work a real player must perform.

After moving the tile composition into a dedicated OpenGL ES pipeline and reducing unnecessary transfers, the 2880 × 1620 offscreen compositor reached 40.04 fps. These are results from different implementations, so the comparison is about the development progression rather than an isolated single-variable speedup. Nevertheless, it demonstrated that where we stitched the tiles could matter almost as much as where we decoded them. That gave us confidence to attempt 3840 × 2160.

Finding a practical stitching strategy

The next question was where the decoded pieces should meet. We explored the following approaches in order of increasing specialization:

ApproachWhat it doesWhy it mattered
FFmpeg-side reconstructionDecode and combine the picture using a general-purpose processing pipelineA useful first proof of concept, but extra frame movement and processing limited the available margin
CPU-side assemblyCopy decoded tiles into a complete frame in system memory before handing it to the graphics cardStraightforward, but potentially expensive at millions of pixels per frame on an Athlon X2
GPU shader stitchingLeave GPU-decoded images in GPU-accessible surfaces, upload only the software-decoded tile and compose the image in a shaderAvoided full-4K CPU copies and let the GPU do scaling and assembly

We ultimately concentrated on the third strategy. The hardware-decoded top tiles remained on the Radeon; the Athlon decoded the bottom tile into system memory; OpenGL ES sampled the three tile textures, selected the appropriate region for each final pixel, and scaled the assembled image for display. The program did not need to materialize a full 3840 × 2160 picture in system RAM.

The reconstruction pipeline evolved through several stitching strategies to reduce CPU processing and unnecessary memory transfers.

This distinction explains a result that would otherwise seem surprising. We were not asking the Athlon to decode, stitch and resize the entire 4K image. It only decoded the part assigned to it. The GPU remained responsible for decoding the other tiles, assembling the image and presenting it.

What we optimized

We iterated through several implementation details:

  1. GPU surface reuse: VA-API hardware decode surfaces were imported into EGL/OpenGL ES textures using DRM PRIME2/DMA-BUF rather than copied back to system RAM. A small cache avoided repeated surface import for recurring VA surfaces; in one 1,079-frame run, the top paths recorded 1,074 cache hits and five misses each.
  2. Direct planar upload: the software-decoded bottom image used three GL_R8 planes for Y, U and V. GL_UNPACK_ROW_LENGTH allowed us to respect FFmpeg line strides without repacking every frame into an intermediate buffer.
  3. Shader composition: OpenGL ES reconstructed the 3840 × 2160 image logically from three tile textures and performed the display downscale on the GPU, with no full-4K CPU readback.
  4. Direct display: the headless server used GBM/EGL and DRM/KMS to present to VGA, bypassing a desktop compositor.
  5. Display/compute overlap: rather than blocking immediately for each DRM page flip, we deferred the wait while preparing the next frame. This recovered useful throughput and reduced idle time.
The hybrid pipeline combined two hardware-decoded upper tiles with one software-decoded lower tile to reconstruct a complete 4K frame.

The 4K tile arrangements

We tried two closely related 4K partitions. The first used two 1920 × 1080 top tiles with a 3840 × 1080 bottom tile. That offscreen configuration reached 25.69 fps, while its paced VGA version reached 23.56 fps. We then shifted the boundary down: two 1920 × 1152 top tiles, with a shorter 3840 × 1008 bottom tile. This transferred more work to the Radeon and reduced the CPU’s assigned pixel count.

The boundary adjustment was small on screen but meaningful for this processor:

Contribution1080-pixel top boundary1152-pixel top boundary
Each GPU tile1920 × 10801920 × 1152
Software-decoded bottom tile3840 × 10803840 × 1008
Pixels assigned to CPU per frame4,147,2003,870,720
Share of 4K pixels decoded by CPU50.0%46.7%

In other words, increasing the height of each GPU tile by 72 pixels removed 276,480 pixels per frame, or 6.7%, from the CPU tile. It did not remove that work from the pipeline; it reassigned it to the HD 6570.

Moving the horizontal tile boundary from 1080 to 1152 pixels transferred additional decoding work from the Athlon to the Radeon.

The 1152-pixel split delivered 26.81 fps offscreen in the initial full-4K test and about 23.75 fps when paced to VGA. The more consequential change was not another split: it was overlapping the DRM page-flip wait with preparation of the next frame. In the 1152-pixel configuration, an unpaced overlapping KMS test reached 28.22 fps over 1,079 frames (38.241 seconds). That is approximately 17.7% more throughput than the 23.976 fps content rate. A paced overlap run measured 23.99 fps, essentially the target average frame rate.

Selected measured results

The record also includes the 25.9 fps early 1620p FFmpeg prototype and 49.81 fps two-tile GPU compositor discussed above; they are not repeated in the 4K table because they processed different frame sizes and included different stages of work.

Successive optimizations improved reconstruction throughput until full-4K processing exceeded the original video’s 23.976 fps frame rate.
ExperimentOffscreen / outputMeasured throughput
Hybrid 2880 × 1620Offscreen40.04 fps
4K, 1080-pixel top tilesOffscreen25.69 fps
4K, 1080-pixel top tilesPaced VGA23.56 fps
4K, 1152-pixel top tilesOffscreen26.81 fps
4K, 1152-pixel top tilesPaced VGA, initial23.75 fps
4K, 1152-pixel top tilesSequential unpaced VGA24.50 fps
4K, 1152-pixel top tilesOverlapping unpaced VGA28.22 fps
4K, 1152-pixel top tilesOverlapping paced VGA23.99 fps

The measurements above were collected during different iterations and should not be read as a perfectly controlled apples-to-apples codec benchmark. Offscreen means frames were reconstructed without waiting for a physical screen; unpaced VGA means the display path was active but the program was not constrained to movie timing; paced VGA means playback was scheduled to the 23.976 fps presentation clock. They do, however, show the progression from a workable concept to full-frame reconstruction with practical 24 fps average throughput.

Why the last optimizations mattered

In the 28.22 fps unpaced overlap run, the accumulated stage timings were approximately 25.13 seconds decoding, 1.80 seconds VA synchronization/import, 5.06 seconds CPU texture upload, and 5.42 seconds GL draw/finish. These are per-stage totals and should not be summed as if they were a single critical path in a fully overlapping system.

Profiling the optimized pipeline illustrates the relative costs of video decoding, synchronization, texture uploads and GPU composition.

The CPU tile remained the expensive part, but the optimization work mattered. Caching VA surfaces avoided unnecessary re-imports. Direct planar texture uploads eliminated row repacking. GPU-side reconstruction avoided a potentially large full-frame system-memory copying path. Finally, deferring the DRM flip wait meant the main loop could use time that had previously been spent waiting for the display.

For scale, a complete 3840 × 2160 image in three-plane 8-bit 4:2:0 format contains approximately 12.4 MB of active pixel samples per frame (before stride or surface overhead). At 24 fps, repeatedly moving even one whole frame through system memory would involve roughly 299 MB/s of pixel data per copy. It is not that the graphics card could never move that much data; it is that avoiding needless transfers is valuable when the rest of the system is already running near its limit.

What we demonstrated — and what we did not

The HD 6570 and Athlon 64 X2 4200+ reconstructed all 3840 × 2160 pixels of the test video inside an OpenGL ES pipeline. On our 1024 × 768 VGA display, the result was downscaled to a letterboxed image. We also achieved average throughput above the 23.976 fps source rate and measured paced presentation close to 24 fps. The earlier stream-capacity and bitrate benchmarks explain why distributing the pixels across the Radeon and Athlon was necessary.

These results do not establish glitch-free playback of every frame. Some displayed intervals were longer than the nominal 41.7 ms movie frame period. Nor do they establish native 4K HDMI output, a general bitrate ceiling, or universal compatibility with arbitrary H.264 files. In one upstream conversion variant we also observed a colour cast; we therefore have not treated end-to-end colour accuracy as validated. The full-4K reconstruction trial used a specific 45-second, 1,079-frame excerpt, so other scenes and encodings may stress the hardware differently. Detailed diagnosis of occasional late frames is outside the scope of this installment.

The useful conclusion is architectural: a graphics card that cannot handle a particular video as one stream may still contribute substantially to its reconstruction when decoding, memory transfer, composition and presentation are treated as separate engineering problems.

Where Part 2 ends

Our final trial for this article was the 1920 × 1152 + 1920 × 1152 + 3840 × 1008 arrangement. The measured result was enough to demonstrate full-resolution internal reconstruction and approximately real-time display on remarkably old hardware. There were still occasional longer display intervals, so this is not a claim of perfectly smooth playback under every condition. The detailed investigation of those intervals belongs to a later stage of the project.

The broader Off-Label Hardware lesson is that a nominal codec or resolution limit is not always the end of the story. Sometimes the more interesting question is which parts of the job the hardware can still do well — and how to arrange the rest around it.

←Part 1 — Making 4K Video More Compatible