lavd/fbdev: move common code to separate file
[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 #include "fbdev_common.h"
32
33 typedef struct {
34     AVClass *class;                   ///< class for private options
35     int xoffset;                      ///< x coordinate of top left corner
36     int yoffset;                      ///< y coordinate of top left corner
37     struct fb_var_screeninfo varinfo; ///< framebuffer variable info
38     struct fb_fix_screeninfo fixinfo; ///< framebuffer fixed info
39     int fd;                           ///< framebuffer device file descriptor
40     int index;                        ///< index of a video stream
41     uint8_t *data;                    ///< framebuffer data
42 } FBDevContext;
43
44 static av_cold int fbdev_write_header(AVFormatContext *h)
45 {
46     FBDevContext *fbdev = h->priv_data;
47     enum AVPixelFormat pix_fmt;
48     AVStream *st = NULL;
49     int ret, flags = O_RDWR;
50
51     for (int i = 0; i < h->nb_streams; i++) {
52         if (h->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
53             if (!st) {
54                 fbdev->index = i;
55                 st = h->streams[i];
56             } else {
57                 av_log(h, AV_LOG_WARNING, "More than one video stream found. First one is used.\n");
58                 break;
59             }
60         }
61     }
62     if (!st) {
63         av_log(h, AV_LOG_ERROR, "No video stream found.\n");
64         return AVERROR(EINVAL);
65     }
66
67     if ((fbdev->fd = avpriv_open(h->filename, flags)) == -1) {
68         ret = AVERROR(errno);
69         av_log(h, AV_LOG_ERROR,
70                "Could not open framebuffer device '%s': %s\n",
71                h->filename, av_err2str(ret));
72         return ret;
73     }
74
75     if (ioctl(fbdev->fd, FBIOGET_VSCREENINFO, &fbdev->varinfo) < 0) {
76         ret = AVERROR(errno);
77         av_log(h, AV_LOG_ERROR, "FBIOGET_VSCREENINFO: %s\n", av_err2str(ret));
78         goto fail;
79     }
80
81     if (ioctl(fbdev->fd, FBIOGET_FSCREENINFO, &fbdev->fixinfo) < 0) {
82         ret = AVERROR(errno);
83         av_log(h, AV_LOG_ERROR, "FBIOGET_FSCREENINFO: %s\n", av_err2str(ret));
84         goto fail;
85     }
86
87     pix_fmt = ff_get_pixfmt_from_fb_varinfo(&fbdev->varinfo);
88     if (pix_fmt == AV_PIX_FMT_NONE) {
89         ret = AVERROR(EINVAL);
90         av_log(h, AV_LOG_ERROR, "Framebuffer pixel format not supported.\n");
91         goto fail;
92     }
93
94     fbdev->data = mmap(NULL, fbdev->fixinfo.smem_len, PROT_WRITE, MAP_SHARED, fbdev->fd, 0);
95     if (fbdev->data == MAP_FAILED) {
96         ret = AVERROR(errno);
97         av_log(h, AV_LOG_ERROR, "Error in mmap(): %s\n", av_err2str(ret));
98         goto fail;
99     }
100
101     return 0;
102   fail:
103     close(fbdev->fd);
104     return ret;
105 }
106
107 static int fbdev_write_packet(AVFormatContext *h, AVPacket *pkt)
108 {
109     FBDevContext *fbdev = h->priv_data;
110     uint8_t *pin, *pout;
111     enum AVPixelFormat fb_pix_fmt;
112     int disp_height;
113     int bytes_to_copy;
114     AVCodecContext *codec_ctx = h->streams[fbdev->index]->codec;
115     enum AVPixelFormat video_pix_fmt = codec_ctx->pix_fmt;
116     int video_width = codec_ctx->width;
117     int video_height = codec_ctx->height;
118     int bytes_per_pixel = ((codec_ctx->bits_per_coded_sample + 7) >> 3);
119     int src_line_size = video_width * bytes_per_pixel;
120
121     if (fbdev->index != pkt->stream_index)
122         return 0;
123
124     if (ioctl(fbdev->fd, FBIOGET_VSCREENINFO, &fbdev->varinfo) < 0)
125         av_log(h, AV_LOG_WARNING,
126                "Error refreshing variable info: %s\n", av_err2str(AVERROR(errno)));
127
128     fb_pix_fmt = ff_get_pixfmt_from_fb_varinfo(&fbdev->varinfo);
129
130     if (fb_pix_fmt != video_pix_fmt) {
131         av_log(h, AV_LOG_ERROR, "Pixel format %s is not supported, use %s\n",
132                av_get_pix_fmt_name(video_pix_fmt), av_get_pix_fmt_name(fb_pix_fmt));
133         return AVERROR(EINVAL);
134     }
135
136     disp_height = FFMIN(fbdev->varinfo.yres, video_height);
137     bytes_to_copy = FFMIN(fbdev->varinfo.xres, video_width) * bytes_per_pixel;
138
139     pin  = pkt->data;
140     pout = fbdev->data +
141            bytes_per_pixel * fbdev->varinfo.xoffset +
142            fbdev->varinfo.yoffset * fbdev->fixinfo.line_length;
143
144     if (fbdev->xoffset) {
145         if (fbdev->xoffset < 0) {
146             if (-fbdev->xoffset >= video_width) //nothing to display
147                 return 0;
148             bytes_to_copy += fbdev->xoffset * bytes_per_pixel;
149             pin -= fbdev->xoffset * bytes_per_pixel;
150         } else {
151             int diff = (video_width + fbdev->xoffset) - fbdev->varinfo.xres;
152             if (diff > 0) {
153                 if (diff >= video_width) //nothing to display
154                     return 0;
155                 bytes_to_copy -= diff * bytes_per_pixel;
156             }
157             pout += bytes_per_pixel * fbdev->xoffset;
158         }
159     }
160
161     if (fbdev->yoffset) {
162         if (fbdev->yoffset < 0) {
163             if (-fbdev->yoffset >= video_height) //nothing to display
164                 return 0;
165             disp_height += fbdev->yoffset;
166             pin -= fbdev->yoffset * src_line_size;
167         } else {
168             int diff = (video_height + fbdev->yoffset) - fbdev->varinfo.yres;
169             if (diff > 0) {
170                 if (diff >= video_height) //nothing to display
171                     return 0;
172                 disp_height -= diff;
173             }
174             pout += fbdev->yoffset * fbdev->fixinfo.line_length;
175         }
176     }
177
178     for (int i = 0; i < disp_height; i++) {
179         memcpy(pout, pin, bytes_to_copy);
180         pout += fbdev->fixinfo.line_length;
181         pin  += src_line_size;
182     }
183
184     return 0;
185 }
186
187 static av_cold int fbdev_write_trailer(AVFormatContext *h)
188 {
189     FBDevContext *fbdev = h->priv_data;
190     munmap(fbdev->data, fbdev->fixinfo.smem_len);
191     close(fbdev->fd);
192     return 0;
193 }
194
195 #define OFFSET(x) offsetof(FBDevContext, x)
196 #define ENC AV_OPT_FLAG_ENCODING_PARAM
197 static const AVOption options[] = {
198     { "xoffset", "set x coordinate of top left corner", OFFSET(xoffset), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, ENC },
199     { "yoffset", "set y coordinate of top left corner", OFFSET(yoffset), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, ENC },
200     { NULL }
201 };
202
203 static const AVClass fbdev_class = {
204     .class_name = "fbdev outdev",
205     .item_name  = av_default_item_name,
206     .option     = options,
207     .version    = LIBAVUTIL_VERSION_INT,
208 };
209
210 AVOutputFormat ff_fbdev_muxer = {
211     .name           = "fbdev",
212     .long_name      = NULL_IF_CONFIG_SMALL("Linux framebuffer"),
213     .priv_data_size = sizeof(FBDevContext),
214     .audio_codec    = AV_CODEC_ID_NONE,
215     .video_codec    = AV_CODEC_ID_RAWVIDEO,
216     .write_header   = fbdev_write_header,
217     .write_packet   = fbdev_write_packet,
218     .write_trailer  = fbdev_write_trailer,
219     .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
220     .priv_class     = &fbdev_class,
221 };