-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathVaapiTranscode.cs
More file actions
178 lines (149 loc) · 7.25 KB
/
Copy pathVaapiTranscode.cs
File metadata and controls
178 lines (149 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
using System;
using FFmpeg.AutoGen;
namespace FFmpeg.Sharp.Example
{
/// <summary>
/// Maps to FFmpeg example: vaapi_transcode.c
/// VAAPI-accelerated video transcoding: decode input video with VAAPI then
/// re-encode it using a specified VAAPI encoder (e.g. h264_vaapi, vp9_vaapi).
/// Usage: args[0] = input, args[1] = encode_codec (e.g. h264_vaapi), args[2] = output
/// </summary>
public unsafe class VaapiTranscode : ExampleBase
{
public VaapiTranscode() { Index = 24; Enable = false; }
public override void Execute()
{
if (args.Length != 3)
{
Console.Error.WriteLine(
"Usage: VaapiTranscode <input_stream> <encode_codec> <output_stream>\n" +
"e.g.: VaapiTranscode input.mp4 h264_vaapi output_h264.mp4");
return;
}
var inFile = args[0];
var encoderName = args[1];
var outFile = args[2];
// ── Decoder (VAAPI-backed) ─────────────────────────────────────────
using var demuxer = MediaDemuxer.Open(inFile);
int videoStream = -1;
MediaCodec decoder = null;
for (int i = 0; i < (int)demuxer.Ref.nb_streams; i++)
{
if (demuxer[i].CodecparRef.codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
decoder = MediaCodec.FindDecoder(demuxer[i].CodecparRef.codec_id);
break;
}
}
if (videoStream < 0 || decoder == null)
throw new Exception("Cannot find a video stream in the input file");
var videoSt = demuxer[videoStream];
// InitHWDeviceContext creates the VAAPI device and wires the get_format
// callback that always picks AV_PIX_FMT_VAAPI.
using var dec = new MediaDecoder(decoder);
dec.SetCodecParameters(ref videoSt.CodecparRef);
if (dec.InitHWDeviceContext(AVHWDeviceType.AV_HWDEVICE_TYPE_VAAPI) == 0)
throw new Exception("Unable to decode this file using VA-API.");
dec.Open();
// ── Encoder (opened lazily after the first decoded frame) ─────────
var encCodec = MediaCodec.FindEncoder(encoderName)
?? throw new Exception($"Could not find encoder '{encoderName}'");
MediaEncoder encoder = null;
// ── Output muxer ──────────────────────────────────────────────────
using var muxer = MediaMuxer.Create(outFile);
bool initialized = false;
using var encPkt = new MediaPacket();
using var frame = new MediaFrame();
using var decPkt = new MediaPacket();
// ── Transcode loop ────────────────────────────────────────────────
int ret;
foreach (var pkt in demuxer.ReadPackets(decPkt))
{
if (pkt.Ref.stream_index != videoStream) continue;
ret = DecEnc(dec, ref encoder, encCodec, frame, encPkt, pkt, muxer,
ref initialized);
if (ret < 0) break;
}
// Flush decoder.
DecEnc(dec, ref encoder, encCodec, frame, encPkt, null, muxer,
ref initialized);
// Flush encoder.
EncodeWrite(encoder, encPkt, null, muxer);
if (initialized)
muxer.WriteTrailer();
// ── Cleanup ───────────────────────────────────────────────────────
encoder?.Dispose();
}
private static int DecEnc(MediaDecoder dec, ref MediaEncoder encoder, MediaCodec encCodec,
MediaFrame frame, MediaPacket encPkt, MediaPacket pkt,
MediaMuxer muxer, ref bool initialized)
{
int ret = dec.SendPacket(pkt);
if (ret < 0)
{
Console.Error.WriteLine($"Error during decoding. Error code: {FFmpegException.GetErrorString(ret)}");
return ret;
}
while (ret >= 0)
{
frame.Unref();
ret = dec.ReceiveFrame(frame);
if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)
return 0;
if (ret < 0)
{
Console.Error.WriteLine($"Error while decoding. Error code: {FFmpegException.GetErrorString(ret)}");
return ret;
}
// Lazily open encoder on first frame (we need hw_frames_ctx from decoder).
if (!initialized)
{
encoder = new MediaEncoder(encCodec);
using var decFrames = dec.GetHWFrames(); // refcounted — encoder takes its own reference
encoder.AttachHWFramesContext(decFrames);
encoder.Ref.time_base = dec.Ref.framerate.ToInvert();
encoder.Ref.pix_fmt = AVPixelFormat.AV_PIX_FMT_VAAPI;
encoder.Ref.width = dec.Ref.width;
encoder.Ref.height = dec.Ref.height;
encoder.Open();
muxer.AddStream(encoder);
muxer.WriteHeader();
initialized = true;
}
ret = EncodeWrite(encoder, encPkt, frame, muxer);
if (ret < 0)
Console.Error.WriteLine("Error during encoding and writing.");
}
return ret;
}
private static int EncodeWrite(MediaEncoder encoder, MediaPacket encPkt,
MediaFrame frame, MediaMuxer muxer)
{
if (encoder == null) return 0; // no frame ever reached the encoder
encPkt.Unref();
int ret = encoder.SendFrame(frame);
if (ret < 0)
{
Console.Error.WriteLine($"Error during encoding. Error code: {FFmpegException.GetErrorString(ret)}");
goto end;
}
while (true)
{
ret = encoder.ReceivePacket(encPkt);
if (ret != 0) break;
encPkt.Ref.stream_index = 0;
// Rescale from the encoder timebase to the output stream timebase and write.
ret = muxer.WritePacket(encPkt, encoder.Ref.time_base);
if (ret < 0)
{
Console.Error.WriteLine($"Error during writing data to output file. Error code: {FFmpegException.GetErrorString(ret)}");
return -1;
}
}
end:
if (ret == ffmpeg.AVERROR_EOF) return 0;
return (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) ? 0 : -1;
}
}
}