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