Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record screen to file #292

Closed
wants to merge 1 commit into from
Closed

Record screen to file #292

wants to merge 1 commit into from

Conversation

igorinov
Copy link
Contributor

@igorinov igorinov commented Oct 9, 2018

This change enables writing AV packets received from the target to a video file without re-encoding.

Copy link
Contributor

@npes87184 npes87184 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I have some advice about insignificant little problems.

@@ -207,14 +211,15 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"output", required_argument, NULL, 'o'},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, the output is too general. In the future, we may add some feature like output format. It make a bit confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--output-file?

@@ -235,6 +240,9 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
return SDL_FALSE;
}
break;
case 'o':
args->outfilename = optarg;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make consistent, use underscore as delimiter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@@ -228,7 +228,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
}

ret = event_loop();
LOGD("quit...");
LOGI("quit...");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, LOGD is good enough.

Copy link
Collaborator

@rom1v rom1v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution.

I compiled it (after changing CODEC_FLAG_GLOBAL_HEADER, which does not exist in my FFmpeg, to AV_CODEC_FLAG_GLOBAL_HEADER) and run it with scrcpy -o test.h264.

A video file is correctly recorded, and can be played with VLC or MPV. That's great an interesting 👍

However, the framerate of the video stream received from the device is (very) variable, and is not respected in the resulting video file. For example, if I navigate to a screen which is "constant" (no pixel is changed) and stay on this screen for 10 seconds, then navigate to another application, in the resulting video file, the 10 seconds are not preserved (it immediately switches to the other application).

This is confirmed by the warning given by mpv:

[lavf] This format is marked by FFmpeg as having no timestamps!
[lavf] FFmpeg will likely make up its own broken timestamps. For
[lavf] video streams you can correct this with:
[lavf]     --no-correct-pts --fps=VALUE
[lavf] with VALUE being the real framerate of the stream. You can
[lavf] expect seeking and buffering estimation to be generally
[lavf] broken as well.

(and by the seekbar behavior both in VLC and mpv)

So to preserve timestamps, the H.264 raw stream should be muxed into a container like mkv or mp4 I guess.

Writing to a file is blocking, so ideally, it should be done in a thread separated from the decoder thread (otherwise it can block/slow down decoding, resulting in frame drop). It does not matter as much as if it were the UI thread though.

As a side note, recording on the client side will include the jitter from communication from the server to the client (see #21 (comment)). Maybe it would be "better" to mux on the server side (only when recording is enabled to avoid additional latency when nothing is recorded). What do you think?

Also, what could we do on device rotation?

@igorinov
Copy link
Contributor Author

Thank you! I am adding muxing to the server, but it only supports Android 8.0+ because MediaMuxer(FileDescriptor fd, int format) is not available in earlier versions. While MPV correctly handles frame size changes in the same video file, creating a new file on rotation may be a better option.

@rom1v
Copy link
Collaborator

rom1v commented Oct 10, 2018

but it only supports Android 8.0+ because MediaMuxer(FileDescriptor fd, int format) is not available in earlier versions

In addition, it says that the "File descriptor must be seekable", which is not the case. It seems it cannot mux into a container that can be streamed in live.

Recording on the client side is still acceptable to me, it just makes me sad that people will use it for recording video of games or whatever, while adb screenrecord /sdcard/file.mp4 would give a better result (timestamps would be more accurate).

But this would still be a great feature to be able to record to mkv locally.

While MPV correctly handles frame size changes in the same video file

Frame size changes in the same video file will probably cause problems (mkv only defines the video size once; however VLC should still handle frame size change properly in theory). screenrecord keeps the original frame size, and resize the content inside this rectangle, which is not great but works.

creating a new file on rotation may be a better option

Yes, but it would require a pattern to generate new files (instead of just a filename).

Why do you think about just storing the video rotated? For example, if we start recording in 1200x720, then rotate to 720x1200, the video would be recorded "rotated" in 1200x720? The first frame would define the dimensions. (This is not that simple though, in theory, the frame size may change to arbitrary dimensions)

@igorinov
Copy link
Contributor Author

In my latest experiment, the server sends headers with packet size and PTS before encoded packets:
https://github.com/igorinov/scrcpy/tree/server-timestamp
The output file looks good in MPV but libav prints too many warnings about incorrect PTS while scrcpy is running.

I like the idea about recording rotated video to keep constant frame size, but is it possible without re-encoding?

@rom1v
Copy link
Collaborator

rom1v commented Oct 12, 2018

(just acking your message, I'll try to review it next week, thank you)

@rom1v rom1v mentioned this pull request Oct 19, 2018
@rom1v
Copy link
Collaborator

rom1v commented Oct 25, 2018

@igorinov I just tested your server-timestamp branch. I think it's a good idea to retrieve the PTS like you did if recording is requested.

It records a file, but unfortunately, the timestamps are still not respected in the resulting file (like #292 (review)). IMO we need to mux in a container like mp4 or mkv.

@rom1v
Copy link
Collaborator

rom1v commented Oct 25, 2018

It records a file, but unfortunately, the timestamps are still not respected in the resulting file

Ah, that's because I saved to test.h264. If I save to test.mp4, timestamps are respected 🎉
However, the mp4 takes a lot of time to open in VLC. And saving to test.mkv does not work.

(Is it possible to specify the target container explicitly?)

@igorinov
Copy link
Contributor Author

igorinov commented Nov 1, 2018

I think it should be recognized by extension, if CONFIG_MATROSKA_MUXER is enabled in the build configuration:

AVOutputFormat ff_matroska_muxer = {
.name = "matroska",
.long_name = NULL_IF_CONFIG_SMALL("Matroska"),
.mime_type = "video/x-matroska",
.extensions = "mkv",

@igorinov
Copy link
Contributor Author

igorinov commented Nov 1, 2018

I tried recording to .mkv on a Linux machine, and the file was played successfully with cvlc.

@rom1v
Copy link
Collaborator

rom1v commented Nov 3, 2018

I tried recording to .mkv on a Linux machine, and the file was played successfully with cvlc.

Arf, on Debian sid, I cannot record to mkv :(

I like your branch, and it would be great to get recording into scrcpy. But there are still some issues to solve.

If I record to mp4, the resulting mp4 file takes a very long time to load in vlc, so it is not "proper" (while a file recorded by adb screenrecord works fine). I don't know what the cause is. Any idea?

If I take your current branch, (after I changed CODEC_FLAG_GLOBAL_HEADER to AV_CODEC_FLAG_GLOBAL_HEADER), I get these warnings:

../app/src/decoder.c: In function ‘run_decoder’:
../app/src/decoder.c:145:13: warning: ‘codec’ is deprecated [-Wdeprecated-declarations]
             outstream->codec = avcodec_alloc_context3(codec);
             ^~~~~~~~~
In file included from ../app/src/decoder.c:3:
/usr/include/x86_64-linux-gnu/libavformat/avformat.h:877:21: note: declared here
     AVCodecContext *codec;
                     ^~~~~
../app/src/decoder.c:146:13: warning: ‘codec’ is deprecated [-Wdeprecated-declarations]
             outstream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
             ^~~~~~~~~
In file included from ../app/src/decoder.c:3:
/usr/include/x86_64-linux-gnu/libavformat/avformat.h:877:21: note: declared here
     AVCodecContext *codec;
                     ^~~~~
../app/src/decoder.c:147:13: warning: ‘codec’ is deprecated [-Wdeprecated-declarations]
             outstream->codec->width = decoder->frame_size.width;
             ^~~~~~~~~
In file included from ../app/src/decoder.c:3:
/usr/include/x86_64-linux-gnu/libavformat/avformat.h:877:21: note: declared here
     AVCodecContext *codec;
                     ^~~~~
../app/src/decoder.c:148:13: warning: ‘codec’ is deprecated [-Wdeprecated-declarations]
             outstream->codec->height = decoder->frame_size.height;
             ^~~~~~~~~
In file included from ../app/src/decoder.c:3:
/usr/include/x86_64-linux-gnu/libavformat/avformat.h:877:21: note: declared here
     AVCodecContext *codec;
                     ^~~~~
../app/src/decoder.c:150:13: warning: ‘codec’ is deprecated [-Wdeprecated-declarations]
             outstream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
             ^~~~~~~~~
In file included from ../app/src/decoder.c:3:
/usr/include/x86_64-linux-gnu/libavformat/avformat.h:877:21: note: declared here
     AVCodecContext *codec;
                     ^~~~~

I have no time to investigate on this currently. I'll try to find some time later. If you have some time and find how to fix these issues, that would be awesome 😉

Thank you.

@rom1v
Copy link
Collaborator

rom1v commented Nov 4, 2018

If I record to mp4, the resulting mp4 file takes a very long time to load in vlc

I just found the problem: the PTS given by bufferInfo.presentationTimeUs on the server is the current timestamp (since epoch). To fix this issue, the timestamp of the first frame must be 0, and all others must be relative to this one (in other words, we must substract the first presentationTimeUs from all PTS).

There are still little problems, for example on quit:

Application provided invalid, non monotonically increasing dts to muxer in stream 0: 3347160 >= 3347160

@rom1v
Copy link
Collaborator

rom1v commented Nov 4, 2018

I tried recording to .mkv on a Linux machine, and the file was played successfully with cvlc.

For me, it fails at avformat_write_header(output_ctx, NULL) which returns AVERROR_INVALIDDATA.

@rom1v rom1v changed the title Enable video file output Record video to file Nov 9, 2018
@rom1v
Copy link
Collaborator

rom1v commented Nov 9, 2018

@igorinov I had some time today to work on it.

I started from your commit, and did these changes:

  • I created a new "class" recorder, so that the recording is not implemented directly by the decoder.
  • I use the non-deprecated API (AVCodecParameters) to pass codec params to the output AVStream.
  • I explicitly select the MP4 muxer (I don't let FFmpeg choose based on the filename, some muxers won't record the video properly, and for some reason I can't write header for matroska muxer, cf Record screen to file #292 (comment)).
  • I use 1/1000000 as time base (since the input provide timestamps in microseconds).
  • I fixed PTS (as explained in Record screen to file #292 (comment)).
  • I set both PTS/DTS to AVPacket before writing it (to avoid FFmpeg warnings).
  • I don't write "config" packets (when bufferInfo.flags has MEDIA_CODEC_FLAG_CONFIG) to output file since they contain no data and do not have any PTS/DTS.
  • I handle display (decode + push frame) before recording (to reduce latency).

Here is my branch: record.

scrcpy -o file.mp4

It correctly records rotation, and plays correctly in VLC 😄

Remaining things I will work on:

  • have absolutely no impact on perfs if recording is not enabled (e.g. provide the original read_packet() function if we do not record).
  • handle the case where we receive only part of the header in read_packet().
  • also use AVStream deprecated API (the one you used) in ifdefs to support older FFmpeg versions.
  • also use deprecated av_oformat_next() in ifdefs (in addition to the new av_muxer_iterate()) to support older FFmpeg versions.
  • fix the warning on exit: Application provided invalid, non monotonically increasing dts to muxer in stream 0: 17164020 >= 17164020.

@rom1v
Copy link
Collaborator

rom1v commented Nov 9, 2018

fix the warning on exit: Application provided invalid, non monotonically increasing dts to muxer in stream 0: 17164020 >= 17164020.

Oh, all PTS are not assigned to the right frame, we immediately use the last PTS read for the current decoded frame.

EDIT: fixed by 0cb16be
EDIT2: not fixed: several calls to read_packet() may occur even before the first av_read_frame() is called, which lead to assign wrong PTS to frames (to reproduce, record a small static region with something like --crop 200:200:0:0).

@npes87184
Copy link
Contributor

npes87184 commented Nov 10, 2018

Hi, @rom1v

I cloned the branch record and tried to build.
I met the building error.

../app/src/recorder.c: In function ‘find_mp4_muxer’:
../app/src/recorder.c:13:19: warning: implicit declaration of function ‘av_muxer_iterate’; did you mean ‘av_buffer_c
reate’? [-Wimplicit-function-declaration]
         oformat = av_muxer_iterate(&opaque);
                   ^~~~~~~~~~~~~~~~
                   av_buffer_create
../app/src/recorder.c:13:17: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
         oformat = av_muxer_iterate(&opaque);
                 ^
[31/32] Linking target app/scrcpy.
FAILED: app/scrcpy
cc  -o app/scrcpy 'app/scrcpy@exe/src_main.c.o' 'app/scrcpy@exe/src_command.c.o' 'app/scrcpy@exe/src_control_event.c
.o' 'app/scrcpy@exe/src_controller.c.o' 'app/scrcpy@exe/src_convert.c.o' 'app/scrcpy@exe/src_decoder.c.o' 'app/scrcp
y@exe/src_device.c.o' 'app/scrcpy@exe/src_file_handler.c.o' 'app/scrcpy@exe/src_fps_counter.c.o' 'app/scrcpy@exe/src
_frames.c.o' 'app/scrcpy@exe/src_input_manager.c.o' 'app/scrcpy@exe/src_lock_util.c.o' 'app/scrcpy@exe/src_net.c.o'
'app/scrcpy@exe/src_recorder.c.o' 'app/scrcpy@exe/src_scrcpy.c.o' 'app/scrcpy@exe/src_screen.c.o' 'app/scrcpy@exe/sr
c_server.c.o' 'app/scrcpy@exe/src_str_util.c.o' 'app/scrcpy@exe/src_tiny_xpm.c.o' 'app/scrcpy@exe/src_sys_unix_comma
nd.c.o' 'app/scrcpy@exe/src_sys_unix_net.c.o' -flto -Wl,--no-undefined -Wl,--as-needed -Wl,-O1 -Wl,--start-group -la
vformat -lavcodec -lavutil -lSDL2 -Wl,--end-group
/tmp/ccxOcwQJ.ltrans0.ltrans.o: In function `run_decoder':
<artificial>:(.text+0x1020): undefined reference to `av_muxer_iterate'
collect2: error: ld returned 1 exit status

/usr/include/x86_64-linux-gnu/libavformat » dpkg -s libavformat-dev                            
Package: libavformat-dev                                                               
Status: install ok installed                                
Priority: optional                                                                                                 
Section: libdevel
Installed-Size: 4985
Maintainer: Ubuntu Developers <[email protected]>
Architecture: amd64
Multi-Arch: same
Source: ffmpeg
Version: 7:3.4.4-0ubuntu0.18.04.1
Depends: libavcodec-dev (= 7:3.4.4-0ubuntu0.18.04.1), libavformat57 (= 7:3.4.4-0ubuntu0.18.04.1), libavutil-dev (= 7
:3.4.4-0ubuntu0.18.04.1), libswresample-dev (= 7:3.4.4-0ubuntu0.18.04.1)

Thanks,
yuchenlin

@rom1v
Copy link
Collaborator

rom1v commented Nov 10, 2018

@npes87184 Thank you for your test.

I cloned the branch record and tried to build.
I met the building error.

Yes, that's the reason I planned to:

also use deprecated av_oformat_next() in ifdefs (in addition to the new av_muxer_iterate()) to support older FFmpeg versions.

😉

@npes87184
Copy link
Contributor

Hi, @rom1v

Thank you for your explanation.
According to your explanation, to fix building error, I simply modify the find_mp4_muxer by this commit recorder: use av_oformat_next to support older FFmpeg and it works.

At the first glance, it works like a charm!
However, when scrcpy receive SIGTERM the mp4 file cannot be played.

Thanks,
yuchenlin

@rom1v
Copy link
Collaborator

rom1v commented Nov 10, 2018

However, when scrcpy receive SIGTERM the mp4 file cannot be played.

Ah yes, this need to be fixed too. Thank you.

Basically, reverting f00c6c5 works, but we need to fix what this commit prevented.

@npes87184
Copy link
Contributor

Or set a flag to call recorder_close when we receive SIGTERM?

@rom1v
Copy link
Collaborator

rom1v commented Nov 10, 2018

@npes87184 c2cb069 😉

@rom1v
Copy link
Collaborator

rom1v commented Nov 11, 2018

I think we have a version ready to be merged: branch record.

I also changed the option name:

scrcpy --record file.mp4
scrcpy -r file.mp4

If you want to test before the merge/release, feedbacks are welcome :)

@rom1v rom1v changed the title Record video to file Record screen to file Nov 11, 2018
rom1v added a commit that referenced this pull request Nov 11, 2018
@rom1v
Copy link
Collaborator

rom1v commented Nov 11, 2018

I merged my record branch into master, and published v1.5 with screen recording 🎉

Thank you very much @igorinov for your initial work, this provided an awesome working base. Your commit is merged as d706c5d.

@rom1v rom1v closed this Nov 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants