NDEVR
API Documentation
GstreamerVulkanWriter.h
1#pragma once
2#include <vulkan/vulkan.h>
3#include <gst/gst.h>
4#include <gst/app/gstappsrc.h>
5#include <string>
6#include <stdexcept>
7#include <cstdint>
8#include <vector>
9#include <cstring>
10
11#ifdef _MSC_VER
12#pragma comment(lib, "gstreamer-1.0.lib")
13#pragma comment(lib, "gstapp-1.0.lib")
14#pragma comment(lib, "gstbase-1.0.lib")
15#pragma comment(lib, "gstvideo-1.0.lib")
16#pragma comment(lib, "gobject-2.0.lib")
17#pragma comment(lib, "glib-2.0.lib")
18#endif
19
20namespace NDEVR
21{
29 {
30 public:
43 GStreamerVulkanWriter(VkPhysicalDevice phys, VkDevice dev, VkQueue q, uint32_t queueFamilyIndex, VkCommandPool commandPool, uint32_t w, uint32_t h, VkFormat fmt, const std::string& outFile, uint32_t fpsNumer = 30, uint32_t fpsDenom = 1)
44 : m_phys(phys), m_dev(dev), m_queue(q), m_qfam(queueFamilyIndex), m_cmdPool(commandPool), m_w(w), m_h(h), m_fmt(fmt), m_fpsN(fpsNumer), m_fpsD(fpsDenom)
45 {
46 if (m_fmt != VK_FORMAT_B8G8R8A8_UNORM && m_fmt != VK_FORMAT_R8G8B8A8_UNORM)
47 throw std::runtime_error("unsupported format");
48 m_stride = 4;
49 m_frameBytes = m_w * m_h * m_stride;
50 createStaging();
51 initGStreamer(outFile);
52 }
53
55 {
56 if (m_pipeline)
57 {
58 if (m_appsrc) gst_app_src_end_of_stream(GST_APP_SRC(m_appsrc));
59 GstBus* bus = gst_element_get_bus(m_pipeline);
60 if (bus)
61 {
62 for (;;)
63 {
64 GstMessage* msg = gst_bus_timed_pop_filtered(bus, 3 * GST_SECOND, (GstMessageType)(GST_MESSAGE_EOS | GST_MESSAGE_ERROR | GST_MESSAGE_STATE_CHANGED));
65 if (!msg) break;
66 gst_message_unref(msg);
67 if (msg && (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS || GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR)) break;
68 }
69 gst_object_unref(bus);
70 }
71 gst_element_set_state(m_pipeline, GST_STATE_NULL);
72 gst_object_unref(m_pipeline);
73 }
74 if (m_map) vkUnmapMemory(m_dev, m_mem);
75 if (m_buf) vkDestroyBuffer(m_dev, m_buf, nullptr);
76 if (m_mem) vkFreeMemory(m_dev, m_mem, nullptr);
77 }
78
83 void pushFrame(VkImage srcImage, VkImageLayout currentLayout, uint64_t frameIndex)
84 {
85 VkCommandBuffer cmd = beginCmd();
86 VkImageMemoryBarrier pre{};
87 pre.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
88 pre.oldLayout = currentLayout;
89 pre.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
90 pre.srcQueueFamilyIndex = m_qfam;
91 pre.dstQueueFamilyIndex = m_qfam;
92 pre.image = srcImage;
93 pre.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
94 pre.subresourceRange.baseMipLevel = 0;
95 pre.subresourceRange.levelCount = 1;
96 pre.subresourceRange.baseArrayLayer = 0;
97 pre.subresourceRange.layerCount = 1;
98 pre.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT;
99 pre.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
100 vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &pre);
101
102 VkBufferImageCopy region{};
103 region.bufferOffset = 0;
104 region.bufferRowLength = 0;
105 region.bufferImageHeight = 0;
106 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
107 region.imageSubresource.mipLevel = 0;
108 region.imageSubresource.baseArrayLayer = 0;
109 region.imageSubresource.layerCount = 1;
110 region.imageOffset = { 0, 0, 0 };
111 region.imageExtent = { m_w, m_h, 1 };
112 vkCmdCopyImageToBuffer(cmd, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_buf, 1, &region);
113
114 VkImageMemoryBarrier post{};
115 post.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
116 post.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
117 post.newLayout = currentLayout;
118 post.srcQueueFamilyIndex = m_qfam;
119 post.dstQueueFamilyIndex = m_qfam;
120 post.image = srcImage;
121 post.subresourceRange = pre.subresourceRange;
122 post.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
123 post.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
124 vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &post);
125
126 endCmdAndWait(cmd);
127
128 GstBuffer* buffer = gst_buffer_new_and_alloc(m_frameBytes);
129 GstMapInfo info{};
130 gst_buffer_map(buffer, &info, GST_MAP_WRITE);
131 std::memcpy(info.data, m_map, m_frameBytes);
132 gst_buffer_unmap(buffer, &info);
133 GST_BUFFER_PTS(buffer) = gst_util_uint64_scale(frameIndex * (uint64_t)m_fpsD, GST_SECOND, m_fpsN);
134 GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale((uint64_t)m_fpsD, GST_SECOND, m_fpsN);
135 gst_app_src_push_buffer(GST_APP_SRC(m_appsrc), buffer);
136 }
137
139 void finish()
140 {
141 if (m_appsrc) gst_app_src_end_of_stream(GST_APP_SRC(m_appsrc));
142 }
143
144 private:
145 VkPhysicalDevice m_phys{};
146 VkDevice m_dev{};
147 VkQueue m_queue{};
148 uint32_t m_qfam{};
149 VkCommandPool m_cmdPool{};
150 uint32_t m_w{}, m_h{}, m_stride{};
151 VkFormat m_fmt{};
152 VkBuffer m_buf{};
153 VkDeviceMemory m_mem{};
154 void* m_map{};
155 VkDeviceSize m_frameBytes{};
156 GstElement* m_pipeline{};
157 GstElement* m_appsrc{};
158 uint32_t m_fpsN{}, m_fpsD{};
159
160 uint32_t findMemoryType(uint32_t bits, VkMemoryPropertyFlags flags)
161 {
162 VkPhysicalDeviceMemoryProperties mp{};
163 vkGetPhysicalDeviceMemoryProperties(m_phys, &mp);
164 for (uint32_t i = 0; i < mp.memoryTypeCount; ++i)
165 {
166 if ((bits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & flags) == flags) return i;
167 }
168 throw std::runtime_error("no memory type");
169 }
170
171 void createStaging()
172 {
173 VkBufferCreateInfo bi{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
174 bi.size = m_frameBytes;
175 bi.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
176 bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
177 if (vkCreateBuffer(m_dev, &bi, nullptr, &m_buf) != VK_SUCCESS) throw std::runtime_error("vkCreateBuffer");
178 VkMemoryRequirements req{};
179 vkGetBufferMemoryRequirements(m_dev, m_buf, &req);
180 VkMemoryAllocateInfo ai{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
181 ai.allocationSize = req.size;
182 ai.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
183 if (vkAllocateMemory(m_dev, &ai, nullptr, &m_mem) != VK_SUCCESS) throw std::runtime_error("vkAllocateMemory");
184 if (vkBindBufferMemory(m_dev, m_buf, m_mem, 0) != VK_SUCCESS) throw std::runtime_error("vkBindBufferMemory");
185 if (vkMapMemory(m_dev, m_mem, 0, m_frameBytes, 0, &m_map) != VK_SUCCESS) throw std::runtime_error("vkMapMemory");
186 }
187
188 VkCommandBuffer beginCmd()
189 {
190 VkCommandBufferAllocateInfo ai{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
191 ai.commandPool = m_cmdPool;
192 ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
193 ai.commandBufferCount = 1;
194 VkCommandBuffer cmd{};
195 if (vkAllocateCommandBuffers(m_dev, &ai, &cmd) != VK_SUCCESS) throw std::runtime_error("vkAllocateCommandBuffers");
196 VkCommandBufferBeginInfo bi{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
197 bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
198 if (vkBeginCommandBuffer(cmd, &bi) != VK_SUCCESS) throw std::runtime_error("vkBeginCommandBuffer");
199 return cmd;
200 }
201
202 void endCmdAndWait(VkCommandBuffer cmd)
203 {
204 if (vkEndCommandBuffer(cmd) != VK_SUCCESS) throw std::runtime_error("vkEndCommandBuffer");
205 VkFenceCreateInfo fi{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
206 VkFence fence{};
207 if (vkCreateFence(m_dev, &fi, nullptr, &fence) != VK_SUCCESS) throw std::runtime_error("vkCreateFence");
208 VkSubmitInfo si{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
209 si.commandBufferCount = 1;
210 si.pCommandBuffers = &cmd;
211 if (vkQueueSubmit(m_queue, 1, &si, fence) != VK_SUCCESS) throw std::runtime_error("vkQueueSubmit");
212 vkWaitForFences(m_dev, 1, &fence, VK_TRUE, UINT64_C(0xffffffffffffffff));
213 vkDestroyFence(m_dev, fence, nullptr);
214 vkFreeCommandBuffers(m_dev, m_cmdPool, 1, &cmd);
215 }
216
217 void initGStreamer(const std::string& outFile)
218 {
219 static bool inited = false;
220 if (!inited) { int argc = 0; char** argv = nullptr; gst_init(&argc, &argv); inited = true; }
221 const char* vfmt = (m_fmt == VK_FORMAT_B8G8R8A8_UNORM) ? "BGRA" : "RGBA";
222 std::string pipe =
223 "appsrc name=src is-live=false format=time do-timestamp=false "
224 "! videoconvert n-threads=0 "
225 "! x264enc tune=zerolatency speed-preset=ultrafast bitrate=5000 key-int-max=60 "
226 "! h264parse "
227 "! mp4mux faststart=true "
228 "! filesink location=\"" + outFile + "\"";
229 GError* err = nullptr;
230 m_pipeline = gst_parse_launch(pipe.c_str(), &err);
231 if (!m_pipeline || err) throw std::runtime_error("gst_parse_launch");
232 m_appsrc = gst_bin_get_by_name(GST_BIN(m_pipeline), "src");
233 if (!m_appsrc) throw std::runtime_error("appsrc not found");
234 GstCaps* caps = gst_caps_new_simple(
235 "video/x-raw",
236 "format", G_TYPE_STRING, vfmt,
237 "width", G_TYPE_INT, (int)m_w,
238 "height", G_TYPE_INT, (int)m_h,
239 "framerate", GST_TYPE_FRACTION, (int)m_fpsN, (int)m_fpsD,
240 NULL);
241 g_object_set(G_OBJECT(m_appsrc),
242 "caps", caps,
243 "stream-type", 0,
244 "format", GST_FORMAT_TIME,
245 "block", TRUE,
246 NULL);
247 gst_caps_unref(caps);
248 gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
249 }
250 };
251}
Captures Vulkan framebuffer images and encodes them to an MP4 video file via GStreamer.
GStreamerVulkanWriter(VkPhysicalDevice phys, VkDevice dev, VkQueue q, uint32_t queueFamilyIndex, VkCommandPool commandPool, uint32_t w, uint32_t h, VkFormat fmt, const std::string &outFile, uint32_t fpsNumer=30, uint32_t fpsDenom=1)
Constructs a writer that encodes Vulkan frames to a video file.
void pushFrame(VkImage srcImage, VkImageLayout currentLayout, uint64_t frameIndex)
Copies a Vulkan image to the staging buffer and pushes it as a video frame.
void finish()
Signals the end of the video stream to the GStreamer pipeline.
The primary namespace for the NDEVR SDK.