Files
IQ.Pilot/system/loggerd/encoder/v4l_decode.cc
T
2026-07-11 09:58:33 -05:00

92 lines
3.5 KiB
C++

// Standalone hardware HEVC decoder for the offroad route viewer, built on comma's tested Venus
// (msm_vidc) decoder from tools/replay/qcom_decoder.cc. ffmpeg's generic hevc_v4l2m2m can't drive
// the downstream msm_vidc interface; MsmVidc has the exact firmware recipe (S_EXT_CTRLS
// STREAM_OUTPUT_MODE/DPB_COLOR_FORMAT, the msm_vidc PORT_SETTINGS_CHANGED event, ION USERPTR
// buffers). We demux the .hevc with libav, feed packets, and stream decoded NV12->rgb24 to stdout.
//
// Usage: v4l_decode <input.hevc>
// stdout: "DIM <w> <h>\n" once, then raw rgb24 frames (w*h*3 bytes each).
// Offroad the decoder + GPU are idle (camerad/modeld stopped), so the viewer can use it.
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
#include "tools/replay/qcom_decoder.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/linux/include/msm_media_info.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <input.hevc>\n", argv[0]);
return 2;
}
AVFormatContext *fmt = avformat_alloc_context();
fmt->probesize = 10 * 1024 * 1024; // raw .hevc needs a big probe to index all frames
if (avformat_open_input(&fmt, argv[1], nullptr, nullptr) != 0 ||
avformat_find_stream_info(fmt, nullptr) < 0) {
fprintf(stderr, "v4l_decode: cannot open %s\n", argv[1]);
return 1;
}
int vstream = av_find_best_stream(fmt, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (vstream < 0) { fprintf(stderr, "v4l_decode: no video stream\n"); return 1; }
AVCodecParameters *cp = fmt->streams[vstream]->codecpar;
if (cp->codec_id != AV_CODEC_ID_HEVC) {
fprintf(stderr, "v4l_decode: only HEVC supported (got codec %d)\n", cp->codec_id);
return 3;
}
const int w = cp->width, h = cp->height;
MsmVidc dec;
if (!dec.init(VIDEO_DEVICE, w, h, V4L2_PIX_FMT_HEVC)) {
fprintf(stderr, "v4l_decode: msm_vidc init failed\n");
return 1;
}
dec.avctx = fmt;
// Decoded-frame target buffer (Venus NV12 layout), matching how MsmVidc copies the capture plane.
const size_t y_stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, w);
const size_t uv_offset = y_stride * h;
VisionBuf out;
out.allocate(uv_offset + y_stride * ((h + 1) / 2));
out.init_yuv(w, h, y_stride, uv_offset);
std::vector<uint8_t> argb((size_t)w * h * 4), rgb((size_t)w * h * 3);
// stdout carries only raw rgb24 frames (so this is a drop-in for the ffmpeg rgb pipe); dims go
// to stderr. The consumer already knows WxH from ffprobe.
fprintf(stderr, "v4l_decode: HW decode %dx%d y_stride=%zu\n", w, h, y_stride);
AVPacket pkt;
int npkt = 0, nframe = 0, rr;
while ((rr = av_read_frame(fmt, &pkt)) == 0) {
if (pkt.stream_index == vstream && pkt.size > 0) {
npkt++;
VisionBuf *res = dec.decodeFrame(&pkt, &out);
if (res != nullptr) {
nframe++;
libyuv::NV12ToARGB(out.y, out.stride, out.uv, out.stride, argb.data(), w * 4, w, h);
// ARGBToRAW gives R,G,B byte order (matches the R8G8B8 texture / ffmpeg rgb24). ARGBToRGB24
// would give B,G,R -> the blue tint / swapped red-blue.
libyuv::ARGBToRAW(argb.data(), w * 4, rgb.data(), w * 3, w, h);
if (fwrite(rgb.data(), 1, rgb.size(), stdout) != rgb.size()) {
av_packet_unref(&pkt);
break; // downstream (player) closed the pipe
}
}
}
av_packet_unref(&pkt);
}
fprintf(stderr, "v4l_decode: done rr=%d pkts=%d frames=%d\n", rr, npkt, nframe);
fflush(stdout);
avformat_close_input(&fmt);
return 0;
}