diff --git a/nx/include/switch/services/grc.h b/nx/include/switch/services/grc.h
index 91deb3d3..e09c2a3d 100644
--- a/nx/include/switch/services/grc.h
+++ b/nx/include/switch/services/grc.h
@@ -23,6 +23,14 @@ typedef struct {
     u8 reserved[0x28];            ///< Unused, always zero.
 } GrcGameMovieId;
 
+/// Stream type values for \ref grcdRead.
+typedef enum {
+    GrcStream_Video = 0,          ///< Video stream with H.264 NAL units. Official sw uses buffer size 0x32000.
+    GrcStream_Audio = 1,          ///< Audio stream with PcmFormat_Int16, 2 channels, and samplerate = 48000Hz. Official sw uses buffer size 0x1000.
+} GrcStream;
+
+// Trimming
+
 /**
  * @brief Creates a \ref GrcGameMovieTrimmer using \ref appletCreateGameMovieTrimmer, uses the cmds from it to trim the specified video, then closes it.
  * @note See \ref appletCreateGameMovieTrimmer for the requirements for using this.
@@ -36,3 +44,29 @@ typedef struct {
  */
 Result grcTrimGameMovie(GrcGameMovieId *dst_movieid, const GrcGameMovieId *src_movieid, size_t tmem_size, const void* thumbnail, s32 start, s32 end);
 
+// grc:d
+
+/// Initialize grc:d.
+Result grcdInitialize(void);
+
+/// Exit grc:d.
+void grcdExit(void);
+
+/// Gets the Service for grc:d.
+Service* grcdGetServiceSession(void);
+
+/// Begins streaming. This must not be called more than once, even from a different service session: otherwise the sysmodule will assert.
+Result grcdBegin(void);
+
+/**
+ * @brief Reads a stream, from the video recording being done of the currently running game title.
+ * @note This will block until data is available. This will hang if there is no game title running which has video capture enabled.
+ * @param[in] stream \ref GrcStream
+ * @param[out] buffer Output buffer.
+ * @param[in] size Max size of the output buffer.
+ * @param[out] unk Unknown.
+ * @param[out] data_size Actual output data size.
+ * @param[out] timestamp Timestamp?
+ */
+Result grcdRead(GrcStream stream, void* buffer, size_t size, u32 *unk, u32 *data_size, u64 *timestamp);
+
diff --git a/nx/source/services/grc.c b/nx/source/services/grc.c
index 3c0d094a..d788e225 100644
--- a/nx/source/services/grc.c
+++ b/nx/source/services/grc.c
@@ -12,6 +12,38 @@
 
 static void _grcGameMovieTrimmerClose(GrcGameMovieTrimmer *t);
 
+static Result _grcCmdNoIO(Service* srv, u64 cmd_id) {
+    IpcCommand c;
+    ipcInitialize(&c);
+
+    struct {
+        u64 magic;
+        u64 cmd_id;
+    } *raw;
+
+    raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
+
+    raw->magic = SFCI_MAGIC;
+    raw->cmd_id = cmd_id;
+
+    Result rc = serviceIpcDispatch(srv);
+
+    if (R_SUCCEEDED(rc)) {
+        IpcParsedCommand r;
+        struct {
+            u64 magic;
+            u64 result;
+        } *resp;
+
+        serviceIpcParse(srv, &r, sizeof(*resp));
+        resp = r.Raw;
+
+        rc = resp->result;
+    }
+
+    return rc;
+}
+
 static Result _grcGetEvent(Service* srv, Event* out_event, u64 cmd_id, bool autoclear) {
     IpcCommand c;
     ipcInitialize(&c);
@@ -228,3 +260,79 @@ Result grcTrimGameMovie(GrcGameMovieId *dst_movieid, const GrcGameMovieId *src_m
     return rc;
 }
 
+// grc:d
+
+static Service g_grcdSrv;
+static u64 g_grcdRefCnt;
+
+Result grcdInitialize(void) {
+    atomicIncrement64(&g_grcdRefCnt);
+
+    if (serviceIsActive(&g_grcdSrv))
+        return 0;
+
+    Result rc = smGetService(&g_grcdSrv, "grc:d");
+
+    if (R_FAILED(rc)) grcdExit();
+
+    return rc;
+}
+
+void grcdExit(void) {
+    if (atomicDecrement64(&g_grcdRefCnt) == 0)
+        serviceClose(&g_grcdSrv);
+}
+
+Service* grcdGetServiceSession(void) {
+    return &g_grcdSrv;
+}
+
+Result grcdBegin(void) {
+    return _grcCmdNoIO(&g_grcdSrv, 1);
+}
+
+Result grcdRead(GrcStream stream, void* buffer, size_t size, u32 *unk, u32 *data_size, u64 *timestamp) {
+    IpcCommand c;
+    ipcInitialize(&c);
+
+    ipcAddRecvBuffer(&c, buffer, size, BufferType_Normal);
+
+    struct {
+        u64 magic;
+        u64 cmd_id;
+        u32 stream;
+    } *raw;
+
+    raw = serviceIpcPrepareHeader(&g_grcdSrv, &c, sizeof(*raw));
+
+    raw->magic = SFCI_MAGIC;
+    raw->cmd_id = 2;
+    raw->stream = stream;
+
+    Result rc = serviceIpcDispatch(&g_grcdSrv);
+
+    if (R_SUCCEEDED(rc)) {
+        IpcParsedCommand r;
+        struct {
+            u64 magic;
+            u64 result;
+            u32 unk;
+            u32 data_size;
+            u64 timestamp;
+        } *resp;
+
+        serviceIpcParse(&g_grcdSrv, &r, sizeof(*resp));
+        resp = r.Raw;
+
+        rc = resp->result;
+
+        if (R_SUCCEEDED(rc)) {
+            if (unk) *unk = resp->unk;
+            if (data_size) *data_size = resp->data_size;
+            if (timestamp) *timestamp = resp->timestamp;
+        }
+    }
+
+    return rc;
+}
+