{"id":415,"date":"2015-03-23T00:54:50","date_gmt":"2015-03-23T00:54:50","guid":{"rendered":"http:\/\/blogs.gentoo.org\/lu_zero\/?p=415"},"modified":"2015-06-05T11:02:33","modified_gmt":"2015-06-05T11:02:33","slug":"decoupling-an-api","status":"publish","type":"post","link":"https:\/\/blogs.gentoo.org\/lu_zero\/2015\/03\/23\/decoupling-an-api\/","title":{"rendered":"Decoupling an API"},"content":{"rendered":"<p>This weekend on #libav-devel we discussed again a bit about the problems with the current core <a href=\"https:\/\/libav.org\/doxygen\/master\/group__libavc.html\">avcodec<\/a> api.<\/p>\n<h2>Current situation<\/h2>\n<h3>Decoding<\/h3>\n<p>We have 3 <a href=\"https:\/\/libav.org\/doxygen\/master\/group__lavc__decoding.html\">decoding functions<\/a> for each of the supported kind of media types: Audio, Video and Subtitles.<\/p>\n<p>Subtitles are already a sore thumb since they are not using AVFrame but a specialized structure, let&#8217;s ignore it for now. Audio and Video share pretty much the same signature:<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"kt\">int<\/span> <span class=\"n\">avcodec_decode_something<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVFrame<\/span> <span class=\"o\">*<\/span><span class=\"n\">f<\/span><span class=\"p\">,<\/span> <span class=\"kt\">int<\/span> <span class=\"o\">*<\/span><span class=\"n\">got_frame<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVPacket<\/span> <span class=\"o\">*<\/span><span class=\"n\">p<\/span><span class=\"p\">)<\/span>\n<\/pre>\n<\/div>\n<p>It takes a <strong>context<\/strong> pointer containing the decoder state, consumes a demuxed <strong>packet<\/strong> and optionally outputs a decoded <em>frame<\/em> containing <em>raw<\/em> data in a certain format (audio samples, a video frame).<\/p>\n<p>The usage model is quite simple it takes <strong>packets<\/strong> and whenever it has enough encoded data to emit a frame it emits one, the <strong>got_frame<\/strong> pointer signals if a frame is ready or more data is needed.<\/p>\n<p><strong>Problem:<\/strong><\/p>\n<blockquote><p>\nWhat if 1 AVPacket is near always enough to output 2 or more <strong>frames<\/strong> of raw data?\n<\/p><\/blockquote>\n<p>This happens with <a href=\"https:\/\/wiki.libav.org\/Blueprint\/MVC\">MVC<\/a> and other real-world scenarios.<\/p>\n<p>In general our current API cannot cope with it cleanly.<\/p>\n<p>While working with the MediaSDK interface from Intel and now with MMAL for the Rasberry Pi, similar problems arisen due the natural parallelism the underlying hardware has.<\/p>\n<h3>Encoding<\/h3>\n<p>We have again 3 <a href=\"https:\/\/libav.org\/doxygen\/master\/group__lavc__encoding.html\">functions<\/a> again Subtitles are somehow different, while Audio and Video are sort of nicely uniform.<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"kt\">int<\/span> <span class=\"n\">avcodec_encode_something<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVPacket<\/span> <span class=\"o\">*<\/span><span class=\"n\">p<\/span><span class=\"p\">,<\/span> <span class=\"k\">const<\/span> <span class=\"n\">AVFrame<\/span> <span class=\"o\">*<\/span><span class=\"n\">f<\/span><span class=\"p\">,<\/span> <span class=\"kt\">int<\/span> <span class=\"o\">*<\/span><span class=\"n\">got_packet<\/span><span class=\"p\">)<\/span>\n<\/pre>\n<\/div>\n<p>It is pretty much the dual of the <strong>decoding<\/strong> function: the context pointer is the same, a <strong>frame<\/strong> of raw data enters and a <strong>packet<\/strong> of encoded data. Again we have a pointer to signal if we had enough data and an encoded <strong>packet<\/strong> had been outputted.<\/p>\n<p><strong>Problem:<\/strong><\/p>\n<blockquote><p>\nAgain we might get multiple AVPacket produced out of a single AVFrame data fed.\n<\/p><\/blockquote>\n<p>This happens when the HEVC <em>&#8220;workaround&#8221;<\/em> to encode interlaced content makes the encoder to output the two separate fields as separate encoded frames.<\/p>\n<p>Again, the API cannot cope with it cleanly and threaded or otherwise parallel encoding fit the model just barely.<\/p>\n<h2>Decoupling the process<\/h2>\n<p>To fix this issue (and make our users life simpler) the <a href=\"https:\/\/wiki.libav.org\/Blueprint\/DecoupledCodecAPI\">idea<\/a> is to split the <strong>feeding<\/strong> data function from the one actually providing the processed data.<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_decode_push<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVPacket<\/span> <span class=\"o\">*<\/span><span class=\"n\">packet<\/span><span class=\"p\">);<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_decode_pull<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVFrame<\/span> <span class=\"o\">*<\/span><span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_decode_need_data<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">);<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_decode_have_data<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">);<\/span>\n<\/pre>\n<\/div>\n<div class=\"codehilite\">\n<pre><span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_encode_push<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVFrame<\/span> <span class=\"o\">*<\/span><span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_encode_pull<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVPacket<\/span> <span class=\"o\">*<\/span><span class=\"n\">packet<\/span><span class=\"p\">);<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_encode_need_data<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">);<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_encode_have_data<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">);<\/span>\n<\/pre>\n<\/div>\n<p>From a single function 4 are provided, why it is simple?<\/p>\n<p>The current workflow is more or less like<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"k\">while<\/span> <span class=\"p\">(<\/span><span class=\"n\">get_packet_from_demuxer<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">pkt<\/span><span class=\"p\">))<\/span> <span class=\"p\">{<\/span>\n    <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_something<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">frame<\/span><span class=\"p\">,<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">got_frame<\/span><span class=\"p\">,<\/span> <span class=\"n\">pkt<\/span><span class=\"p\">);<\/span>\n    <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">got_frame<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">render_frame<\/span><span class=\"p\">(<\/span><span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">manage_error<\/span><span class=\"p\">(<\/span><span class=\"n\">ret<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre>\n<\/div>\n<p>The <code>get_packet_from_demuxer()<\/code> is a function that dequeues from some <strong>queue<\/strong> the encoded data or directly call the demuxer (beware: having your I\/O-intensive demuxer function <strong>blocking<\/strong> your CPU-intensive decoding function isn&#8217;t nice), <code>render_frame()<\/code> is as well either something directly talking to some kind of I\/O-subsystem or enqueuing the data to have the actual <strong>rendering<\/strong> (including format conversion, overlaying and scaling) in another thread.<\/p>\n<p>The new API makes much easier to keep the multiple area of concern separated, so they won&#8217;t trip each other while the casual user would have something like<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"k\">while<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">while<\/span> <span class=\"p\">((<\/span><span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_need_data<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">))<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">get_packet_from_demuxer<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">pkt<\/span><span class=\"p\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n           <span class=\"p\">...<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_push<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">pkt<\/span><span class=\"p\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n           <span class=\"p\">...<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"k\">while<\/span> <span class=\"p\">((<\/span><span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_have_data<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">))<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_pull<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n           <span class=\"p\">...<\/span>\n        <span class=\"n\">render_frame<\/span><span class=\"p\">(<\/span><span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre>\n<\/div>\n<p>That has probably few more lines.<\/p>\n<h2>Asyncronous API<\/h2>\n<p>Since the decoupled API is that simple, is possible to craft something more immediate for the casual user.<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"k\">typedef<\/span> <span class=\"k\">struct<\/span> <span class=\"n\">AVCodecDecodeCallback<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"p\">(<\/span><span class=\"o\">*<\/span><span class=\"n\">pull_packet<\/span><span class=\"p\">)(<\/span><span class=\"kt\">void<\/span> <span class=\"o\">*<\/span><span class=\"n\">priv<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVPacket<\/span> <span class=\"o\">*<\/span><span class=\"n\">pkt<\/span><span class=\"p\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"p\">(<\/span><span class=\"o\">*<\/span><span class=\"n\">push_frame<\/span><span class=\"p\">)(<\/span><span class=\"kt\">void<\/span> <span class=\"o\">*<\/span><span class=\"n\">priv<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVFrame<\/span> <span class=\"o\">*<\/span><span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n    <span class=\"kt\">void<\/span> <span class=\"o\">*<\/span><span class=\"n\">priv_data<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span> <span class=\"n\">AVCodecDecodeCallback<\/span><span class=\"p\">;<\/span>\n\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_register_decode_callbacks<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">AVCodecDecodeCallback<\/span> <span class=\"o\">*<\/span><span class=\"n\">cb<\/span><span class=\"p\">);<\/span>\n\n<span class=\"kt\">int<\/span> <span class=\"nf\">avcodec_decode_loop<\/span><span class=\"p\">(<\/span><span class=\"n\">AVCodecContext<\/span> <span class=\"o\">*<\/span><span class=\"n\">avctx<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n    <span class=\"n\">AVCodecDecodeCallback<\/span> <span class=\"o\">*<\/span><span class=\"n\">cb<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avctx<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">cb<\/span><span class=\"p\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">ret<\/span><span class=\"p\">;<\/span>\n    <span class=\"k\">while<\/span> <span class=\"p\">((<\/span><span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_need_data<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">))<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">cb<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">pull_packet<\/span><span class=\"p\">(<\/span><span class=\"n\">cb<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">priv_data<\/span><span class=\"p\">,<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">pkt<\/span><span class=\"p\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n            <span class=\"k\">return<\/span> <span class=\"n\">ret<\/span><span class=\"p\">;<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_push<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">pkt<\/span><span class=\"p\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n            <span class=\"k\">return<\/span> <span class=\"n\">ret<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"k\">while<\/span> <span class=\"p\">((<\/span><span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_have_data<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">))<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_pull<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n            <span class=\"k\">return<\/span> <span class=\"n\">ret<\/span><span class=\"p\">;<\/span>\n        <span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">cb<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">push_frame<\/span><span class=\"p\">(<\/span><span class=\"n\">cb<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">priv_data<\/span><span class=\"p\">,<\/span> <span class=\"n\">frame<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">ret<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/pre>\n<\/div>\n<p>So the actual minimum decoding loop can be just 2 calls:<\/p>\n<div class=\"codehilite\">\n<pre><span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_register_decode_callbacks<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">,<\/span> <span class=\"n\">cb<\/span><span class=\"p\">);<\/span>\n<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">ret<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n   <span class=\"p\">...<\/span>\n<span class=\"k\">while<\/span> <span class=\"p\">((<\/span><span class=\"n\">ret<\/span> <span class=\"o\">=<\/span> <span class=\"n\">avcodec_decode_loop<\/span><span class=\"p\">(<\/span><span class=\"n\">avctx<\/span><span class=\"p\">))<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">);<\/span>\n<\/pre>\n<\/div>\n<p>Cute, isn&#8217;t it?<\/p>\n<h2>Theory is simple &#8230;<\/h2>\n<p>&#8230; the practice not so much:<br \/>\n&#8211; there are plenty of implementation issues to take in account.<br \/>\n&#8211; <strong>LOTS<\/strong> of tedious work converting all the codecs to the new API.<br \/>\n&#8211; lots of details to iron out (e.g. <code>have_data()<\/code> and <code>need_data()<\/code> should block or not?)<\/p>\n<p>We did radical overhauls before, such as introducing <strong>reference-counted<\/strong> AVFrames thanks to Anton, so we aren&#8217;t much scared of reshaping and cleaning the codebase once more.<\/p>\n<p>If you like the ideas posted above or you want to discuss them more, you can join the Libav <a href=\"irc:\/\/irc.freenode.net\/libav-devel\">irc channel<\/a> or <a href=\"https:\/\/lists.libav.org\/mailman\/listinfo\/libav-devel\/\">mailing list<\/a> to discuss and help.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This weekend on #libav-devel we discussed again a bit about the problems with the current core avcodec api. Current situation Decoding We have 3 decoding functions for each of the supported kind of media types: Audio, Video and Subtitles. Subtitles are already a sore thumb since they are not using AVFrame but a specialized structure, &hellip; <a href=\"https:\/\/blogs.gentoo.org\/lu_zero\/2015\/03\/23\/decoupling-an-api\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Decoupling an API<\/span><\/a><\/p>\n","protected":false},"author":10,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[14,6],"tags":[19],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p1aGWH-6H","_links":{"self":[{"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/posts\/415"}],"collection":[{"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/comments?post=415"}],"version-history":[{"count":6,"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/posts\/415\/revisions"}],"predecessor-version":[{"id":467,"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/posts\/415\/revisions\/467"}],"wp:attachment":[{"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/media?parent=415"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/categories?post=415"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.gentoo.org\/lu_zero\/wp-json\/wp\/v2\/tags?post=415"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}