lavd: add fbdev output device
[ffmpeg.git] / libavdevice / fbdev_enc.c
1 /*
2  * Copyright (c) 2013 Lukasz Marek
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <sys/ioctl.h>
24 #include <sys/mman.h>
25 #include <linux/fb.h>
26 #include "libavutil/pixdesc.h"
27 #include "libavutil/log.h"
28 #include "libavutil/mem.h"
29 #include "libavutil/opt.h"
30 #include "libavformat/avformat.h"
31
32 typedef struct {
33     AVClass *class;                   ///< class for private options
34     int xoffset;                      ///< x coordinate of top left corner
35     int yoffset;                      ///< y coordinate of top left corner
36     struct fb_var_screeninfo varinfo; ///< framebuffer variable info
37     struct fb_fix_screeninfo fixinfo; ///< framebuffer fixed info
38     int fd;                           ///< framebuffer device file descriptor
39     int index;                        ///< index of a video stream
40     uint8_t *data;                    ///< framebuffer data
41 } FBDevContext;
42
43 struct rgb_pixfmt_map_entry {
44     int bits_per_pixel;
45     int red_offset, green_offset, blue_offset, alpha_offset;
46     enum AVPixelFormat pixfmt;
47 };
48
49 static const struct rgb_pixfmt_map_entry rgb_pixfmt_map[] = {
50     // bpp, red_offset,  green_offset, blue_offset, alpha_offset, pixfmt
51     {  32,       0,           8,          16,           24,   AV_PIX_FMT_RGBA  },
52     {  32,      16,           8,           0,           24,   AV_PIX_FMT_BGRA  },
53     {  32,       8,          16,          24,            0,   AV_PIX_FMT_ARGB  },
54     {  32,       3,           2,           8,            0,   AV_PIX_FMT_ABGR  },
55     {  24,       0,           8,          16,            0,   AV_PIX_FMT_RGB24 },
56     {  24,      16,           8,           0,            0,   AV_PIX_FMT_BGR24 },
57     {  16,      11,           5,           0,           16,   AV_PIX_FMT_RGB565 },
58 };
59
60 static enum AVPixelFormat get_pixfmt_from_fb_varinfo(struct fb_var_screeninfo *varinfo)
61 {
62     int i;
63
64     for (i = 0; i < FF_ARRAY_ELEMS(rgb_pixfmt_map); i++) {
65         const struct rgb_pixfmt_map_entry *entry = &rgb_pixfmt_map[i];
66         if (entry->bits_per_pixel == varinfo->bits_per_pixel &&
67             entry->red_offset     == varinfo->red.offset     &&
68             entry->green_offset   == varinfo->green.offset   &&
69             entry->blue_offset    == varinfo->blue.offset)
70             return entry->pixfmt;
71     }
72
73     return AV_PIX_FMT_NONE;
74 }
75
76 static av_cold int fbdev_write_header(AVFormatContext *h)
77 {
78     FBDevContext *fbdev = h->priv_data;
79     enum AVPixelFormat pix_fmt;
80     AVStream *st = NULL;
81     int ret, flags = O_RDWR;
82
83     for (int i = 0; i < h->nb_streams; i++) {
84         if (h->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
85             if (!st) {
86                 fbdev->index = i;
87                 st = h->streams[i];
88             } else {
89                 av_log(h, AV_LOG_WARNING, "More than one video stream found. First one is used.\n");
90                 break;
91             }
92         }
93     }
94     if (!st) {
95         av_log(h, AV_LOG_ERROR, "No video stream found.\n");
96         return AVERROR(EINVAL);
97     }
98
99     if ((fbdev->fd = avpriv_open(h->filename, flags)) == -1) {
100         ret = AVERROR(errno);
101         av_log(h, AV_LOG_ERROR,
102                "Could not open framebuffer device '%s': %s\n",
103                h->filename, av_err2str(ret));
104         return ret;
105     }
106
107     if (ioctl(fbdev->fd, FBIOGET_VSCREENINFO, &fbdev->varinfo) < 0) {
108         ret = AVERROR(errno);
109         av_log(h, AV_LOG_ERROR, "FBIOGET_VSCREENINFO: %s\n", av_err2str(ret));
110         goto fail;
111     }
112
113     if (ioctl(fbdev->fd, FBIOGET_FSCREENINFO, &fbdev->fixinfo) < 0) {
114         ret = AVERROR(errno);
115         av_log(h, AV_LOG_ERROR, "FBIOGET_FSCREENINFO: %s\n", av_err2str(ret));
116         goto fail;
117     }
118
119     pix_fmt = get_pixfmt_from_fb_varinfo(&fbdev->varinfo);
120     if (pix_fmt == AV_PIX_FMT_NONE) {
121         ret = AVERROR(EINVAL);
122         av_log(h, AV_LOG_ERROR, "Framebuffer pixel format not supported.\n");
123         goto fail;
124     }
125
126     fbdev->data = mmap(NULL, fbdev->fixinfo.smem_len, PROT_WRITE, MAP_SHARED, fbdev->fd, 0);
127     if (fbdev->data == MAP_FAILED) {
128         ret = AVERROR(errno);
129         av_log(h, AV_LOG_ERROR, "Error in mmap(): %s\n", av_err2str(ret));
130         goto fail;
131     }
132
133     return 0;
134   fail:
135     close(fbdev->fd);
136     return ret;
137 }
138
139 static int fbdev_write_packet(AVFormatContext *h, AVPacket *pkt)
140 {
141     FBDevContext *fbdev = h->priv_data;
142     uint8_t *pin, *pout;
143     enum AVPixelFormat fb_pix_fmt;
144     int disp_height;
145     int bytes_to_copy;
146     AVCodecContext *codec_ctx = h->streams[fbdev->index]->codec;
147     enum AVPixelFormat video_pix_fmt = codec_ctx->pix_fmt;
148     int video_width = codec_ctx->width;
149     int video_height = codec_ctx->height;
150     int bytes_per_pixel = ((codec_ctx->bits_per_coded_sample + 7) >> 3);
151     int src_line_size = video_width * bytes_per_pixel;
152
153     if (fbdev->index != pkt->stream_index)
154         return 0;
155
156     if (ioctl(fbdev->fd, FBIOGET_VSCREENINFO, &fbdev->varinfo) < 0)
157         av_log(h, AV_LOG_WARNING,
158                "Error refreshing variable info: %s\n", av_err2str(AVERROR(errno)));
159
160     fb_pix_fmt = get_pixfmt_from_fb_varinfo(&fbdev->varinfo);
161
162     if (fb_pix_fmt != video_pix_fmt) {
163         av_log(h, AV_LOG_ERROR, "Pixel format %s is not supported, use %s\n",
164                av_get_pix_fmt_name(video_pix_fmt), av_get_pix_fmt_name(fb_pix_fmt));
165         return AVERROR(EINVAL);
166     }
167
168     disp_height = FFMIN(fbdev->varinfo.yres, video_height);
169     bytes_to_copy = FFMIN(fbdev->varinfo.xres, video_width) * bytes_per_pixel;
170
171     pin  = pkt->data;
172     pout = fbdev->data +
173            bytes_per_pixel * fbdev->varinfo.xoffset +
174            fbdev->varinfo.yoffset * fbdev->fixinfo.line_length;
175
176     if (fbdev->xoffset) {
177         if (fbdev->xoffset < 0) {
178             if (-fbdev->xoffset >= video_width) //nothing to display
179                 return 0;
180             bytes_to_copy += fbdev->xoffset * bytes_per_pixel;
181             pin -= fbdev->xoffset * bytes_per_pixel;
182         } else {
183             int diff = (video_width + fbdev->xoffset) - fbdev->varinfo.xres;
184             if (diff > 0) {
185                 if (diff >= video_width) //nothing to display
186                     return 0;
187                 bytes_to_copy -= diff * bytes_per_pixel;
188             }
189             pout += bytes_per_pixel * fbdev->xoffset;
190         }
191     }
192
193     if (fbdev->yoffset) {
194         if (fbdev->yoffset < 0) {
195             if (-fbdev->yoffset >= video_height) //nothing to display
196                 return 0;
197             disp_height += fbdev->yoffset;
198             pin -= fbdev->yoffset * src_line_size;
199         } else {
200             int diff = (video_height + fbdev->yoffset) - fbdev->varinfo.yres;
201             if (diff > 0) {
202                 if (diff >= video_height) //nothing to display
203                     return 0;
204                 disp_height -= diff;
205             }
206             pout += fbdev->yoffset * fbdev->fixinfo.line_length;
207         }
208     }
209
210     for (int i = 0; i < disp_height; i++) {
211         memcpy(pout, pin, bytes_to_copy);
212         pout += fbdev->fixinfo.line_length;
213         pin  += src_line_size;
214     }
215
216     return 0;
217 }
218
219 static av_cold int fbdev_write_trailer(AVFormatContext *h)
220 {
221     FBDevContext *fbdev = h->priv_data;
222     munmap(fbdev->data, fbdev->fixinfo.smem_len);
223     close(fbdev->fd);
224     return 0;
225 }
226
227 #define OFFSET(x) offsetof(FBDevContext, x)
228 #define ENC AV_OPT_FLAG_ENCODING_PARAM
229 static const AVOption options[] = {
230     { "xoffset", "set x coordinate of top left corner", OFFSET(xoffset), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, ENC },
231     { "yoffset", "set y coordinate of top left corner", OFFSET(yoffset), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, ENC },
232     { NULL }
233 };
234
235 static const AVClass fbdev_class = {
236     .class_name = "fbdev outdev",
237     .item_name  = av_default_item_name,
238     .option     = options,
239     .version    = LIBAVUTIL_VERSION_INT,
240 };
241
242 AVOutputFormat ff_fbdev_muxer = {
243     .name           = "fbdev",
244     .long_name      = NULL_IF_CONFIG_SMALL("Linux framebuffer"),
245     .priv_data_size = sizeof(FBDevContext),
246     .audio_codec    = AV_CODEC_ID_NONE,
247     .video_codec    = AV_CODEC_ID_RAWVIDEO,
248     .write_header   = fbdev_write_header,
249     .write_packet   = fbdev_write_packet,
250     .write_trailer  = fbdev_write_trailer,
251     .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
252     .priv_class     = &fbdev_class,
253 };