PHP-FFMpeg / PHP-FFMpeg

An object oriented PHP driver for FFMpeg binary
MIT License
4.83k stars 885 forks source link

Apple iDevices do not display mp4 videos when served without special treatment #304

Closed LaurentMarquet closed 5 years ago

LaurentMarquet commented 7 years ago
Q A
Bug? no
New Feature? no
Version Used Not related to
FFmpeg Version Not related to
OS Not related to

According to http://www.iphonefaq.org/archives/97961 (I've also found this information in other places) videos have to be encoded with H.264 video up to 720p, 30 fps and audio: AAC-LC up to 160 Kbps, 48kHz, stereo, to be viewable from iDevices.

When I encode the video using x264 + libfdk_aac and gets information from VLC, I have for audio MPEG AAC Audio (mp4a) / 44100 Hz / 16 bits / 1411 kbit/s.

It seems that MPEG AAC and AAC-LC are the same but it looks like the KiloBitrate is too much. I've found an option to set it when saving only the audio, but can't find one when saving the video. Is there one ?

Is there a specific configuration to be able to save video to be viewable from iDevices ? Thanks

Romain commented 7 years ago

Hi @Laurent3170, There is currently no option to set the KiloBitrate option in the X264 interface. Have you tried to use another format, like MP3 instead of AAC?

$format->setAudioCodec("libmp3lame");

As I'm not an expert on iOS formats, I don't know any specific configuration for these devices.

LaurentMarquet commented 7 years ago

Hi @Romain, thanks for your answer. Yes I was using libmp3lame before libfdk_aac. I have changed to libfdk_aac because of the link above specifying that AAC-LC is needed. I have also tried with aac, libvo_aacenc and libfaac, but the result is the same: not able to view the video on iDevices...

LaurentMarquet commented 7 years ago

Do you know where I can find help ? I don't think I'm alone to have the need to have videos viewable on iDevices ;-)

Romain commented 7 years ago

I'm unsure. The best way would be for you to figure out what the ffmpeg command should be and post it here, so we can consider modifying the bundle to either create a new feature for iDevices or simply give you the ability to change the KiloBitrate for videos.

LaurentMarquet commented 7 years ago

Thanks. The thing is that I don't know what my command line should look like... But I'll make investigations.

LaurentMarquet commented 7 years ago

I have found https://korben.info/ffmpeg-pour-les-nuls.html (in french) that specifies the command line for iPhone ffmpeg -i source_video.avi input -acodec aac -ab 128kb -vcodec mpeg4 -b 1200kb -mbd 2 -flags +4mv+trell -aic 2 -cmp 2 -subcmp 2 -s 320x180 -title X final_video.mp4. I have tested it but I have errors and for the time being it's still chinese for me as I have to know what means every flag... If it talks to you... I will continue investigations but next week, as children are on holidays.

Romain commented 7 years ago

Hi @Laurent3170, Thanks for this follow-up. Feel free to paste any error here, so we can help with it.

LaurentMarquet commented 7 years ago

I've made some tests. By using ffmpeg -i video-master.mov -c:v mpeg4 -r 24 -b:v 160k -c:a aac -b:a 160k -strict -2 -s 640x400 video.mp4 I should have an audio bitrate of 160kbps but I obtain 1411kbps... I've tried to lower the -b:a value but the result is still 1411 kbps. Maybe it's due to the fact that I use a quite old version ffmpeg version 2.8.11-0ubuntu0.16.04.1. I'm trying to install a newer version but its' not that easy...

jens1o commented 7 years ago

Hi @Laurent3170, There is currently no option to set the KiloBitrate option in the X264 interface.

May I create a PR? I'd searched after this, but didn't found anything.

Romain commented 7 years ago

The function is present in the DefaultVideo class, but that's right, not in the H264 one. Yes a PR would be helpful. Thanks!

jens1o commented 7 years ago

@Romain But it's used in the examples? And since it's just extending, why it doesn't inherit these functions? https://github.com/PHP-FFMpeg/PHP-FFMpeg#transcoding

Romain commented 7 years ago

@jens1o You're right, I answered a bit fast.

class X264 extends DefaultVideo

No need to recreate one. The example is valid.

When @Laurent3170 says:

By using ffmpeg -i video-master.mov -c:v mpeg4 -r 24 -b:v 160k -c:a aac -b:a 160k -strict -2 -s 640x400 video.mp4 I should have an audio bitrate of 160kbps but I obtain 1411kbps...

This is not related to the bundle but to FFMPEG itself. I think you should make sure your version of FFMPEG is working properly, @Laurent3170 .

LaurentMarquet commented 7 years ago

I have updated to ffmpeg version 3.2.4-1~16.04.york1 on my Ubuntu computer but the result with the ffmpeg command line is till the same... Don't know how to do better.

I have also tried

$video
    ->filters()
    ...
    ->setKiloBitrate(128)
    ...

$video
    ->save(new X264('libfdk_aac'), 'filenameMp4');

But I receive this error message Attempted to call an undefined method named "setKiloBitrate" of class "FFMpeg\Filters\Video\VideoFilters"

Romain commented 7 years ago

@Laurent3170, the setKiloBitrate function must be applied on a format, not on the video... ;) See the example provided by @jens1o

LaurentMarquet commented 7 years ago

Ah... It works better like this! I still can't read the video on iDevices, but now I can search and "play" with data. I'll let you know. Additionally, I've found extra-spaces in the examples, I've made a PR (#318) to correct them.

Romain commented 7 years ago

Hehe, sure it's better... ;o) Thanks for the PR, I'll look at it!

LaurentMarquet commented 7 years ago

So, I'm still fighting with videos... I have a video that works on iPhone (but not made by me): https://edlo.eu/images/participatif.mp4 and the other one still not working: https://edlo.eu/6gkfwk/img/video.mp4. They are coded both using video: H264 - MPEG-4 AVC (part 10) (avc1) and audio: MPEG AAC Audio (mp4a) 44100 Hz. I don't know what to do... If you can help, it would be greatly appreciated!

Romain commented 7 years ago

Hi @Laurent3170, I'm not an expert on iPhone encoding... I'll check if I can find some help on this...

Romain commented 7 years ago

I asked @FranckHAEGELI, who thinks that it may come from number of frame per seconds (30 when iOS only accept 23,976 , 25 or 29,97, but it's not sure that this is the problem...

LaurentMarquet commented 7 years ago

Thanks for the search. Unfortunately it doesn't work. My framerate was 24. I have tested with 23,976, 25 and 29,97, but it's the same, I can't read on a iPhone. I think it's related to the sound codec, but apart with VLC, I don't know how to have all the encoding's details of a video, to check the difference between the two videos I've cited above. I've googled a while ago but it was not giving a "good" result, I'll try again later. Thanks again

LaurentMarquet commented 7 years ago

I've found mediainfo which gave me information about the two video above that I have compared. Here is the result (just the meaningful differences):

Video Ok = not encoded via this library Video Nok = video encoded via this library with this code:

$framerate = new FrameRate(25);
$gop = 48;
$maximumTime = 90;
//Calculation to define $finalWidth, $finalHeight and $angle
$dimensions = new Dimension($finalWidth, $finalHeight);

$ffmpeg = FFMpeg::create(array(
    'timeout' => $timeOut - 10,
    'ffmpeg.threads' => 5,
));
$video = $ffmpeg->open($file);

//Resizes video
$video
    ->filters()
    ->clip(TimeCode::fromSeconds(0), TimeCode::fromSeconds($maximumTime))
    ->framerate($framerate, $gop)
    ->addMetadata(['title' => 'Title'])
    ->rotate($angle)
    ->resize($dimensions, $resizeMode, true)
    ->watermark(__DIR__.'/../XXX.png', array(
        'position' => 'relative',
        'bottom' => 50,
        'right' => 50,
        ))
    ->synchronize();

//Defines format
$format = new X264();
$format
    ->setAudioCodec('aac')
    ->setKiloBitrate(1200)
    ->setAudioChannels(2)
    ->setAudioKiloBitrate(126);

//Saves video
$video->save($format, $filenameMp4);

Result Object | Video Ok | Video Nok isStreamable | Yes | No Codec profile | Main@L4 | High@L2.1 Codec settings | CABAC / 1 Ref Frames | CABAC / 6 Ref Frames

So, the main thing is that the video seems to be NOT streamable which may explain why I can't read, and the other about the Codec don't talk to me. I'll investigate about the streamable part.

Romain commented 7 years ago

This definitely looks like an encoding problem. Thanks for investigating on this and sharing your results, it's really nice to you.

jens1o commented 7 years ago

I think I'd might got it.

https://trac.ffmpeg.org/wiki/Encode/H.264#Compatibility

Use simple filters to get what you want 😉

But I'm already making a pr for this.

LaurentMarquet commented 7 years ago

Really thanks for your work! I have tested with the default value (main & 3.1) which should be ok for iPhone 4s but it still doesn't work :-( The format seems to be well coded as, when I do a dump, I obtain

FFMpeg\Format\Video\X264 {#1138
  -bframesSupport: true
  -passes: 2
  #videoCodec: "libx264"
  #kiloBitrate: 1200
  #modulus: 16
  -profile: "main"
  -level: 3.1
  #additionalParamaters: null
  #audioCodec: "aac"
  #audioKiloBitrate: 126
  #audioChannels: 2
  #listeners: []
}

I have also tried

$format
    ->setAudioCodec('aac')
    ->setKiloBitrate(1200)
    ->setAudioChannels(2)
    ->setAudioKiloBitrate(126)
    ->setProfile(Profile::HIGH)
    ->setLevel(4.1);

But then I obtain this error (but here it may be due to a bad copy/paste, event if I've checked)

[Symfony\Component\Debug\Exception\FatalThrowableError]  
Call to a member function setLevel() on null 
jens1o commented 7 years ago

Oh. Thanks for searching this, there is a mistake... You'd found a bug!

LaurentMarquet commented 7 years ago

Finding a bug (without knowing it) is not the main part of the problem ;-) Thanks to you!

Romain commented 7 years ago

@Laurent3170 could you paste here the complete logs so we can figure out where the error resides?

LaurentMarquet commented 7 years ago

@Romain The only logs I can produce are the ones produced by the Command I use to resize the videos, which are not useful as they only bring the error mentioned above. Can you explain me how to produce the logs you wanna see?

Romain commented 7 years ago

Ok, I misunderstood your previous message, you already pasted the logs you had.

Can you confirm that you're using your PR to do this:

$format
    ->setProfile(Profile::HIGH)
    ->setLevel(4.1);

?

Why is $format NULL? Haven't you this before? $format = new X264();

LaurentMarquet commented 7 years ago

@Romain Yes I have copied/pasted the changes from #335 on my computer and then run the script. When I don't use setProfile and setLevel the video is encoded but not working on iDevices and when I use those functions the format is set as null, but There are some changes on #335 since my message above, that I haven't tested yet.

LaurentMarquet commented 7 years ago

The changes were just about the test. So the result is still the same for me, not being displayed oniPhone 4s and $format = null

dmanthing commented 7 years ago

You must create format first then set level and profile last. Also i use baseline and level 3.0

LaurentMarquet commented 7 years ago

Yes, I do

$format = new X264();
$format
    ->setAudioCodec('aac')
    ->setKiloBitrate(1200)
    ->setAudioChannels(2)
    ->setAudioKiloBitrate(126)
    ->setProfile(Profile::HIGH)
    ->setLevel(4.1);

But like this I have an error

jens1o commented 7 years ago

he uses baseline and level 3.0, you're using profile high and level 4.1

LaurentMarquet commented 7 years ago

Yes this was due to a bad copy/paste from the code above. But I have tried with baseline and 3.0, the $format is not null so this error has disappeared :-) the video is well encoded, but I still can't read the video https://edlo.eu/6gkfwk/img/video.mp4.

LaurentMarquet commented 7 years ago

Any news ? I've tried with an iPhone 6 and still the video is not readable...

jens1o commented 7 years ago

I'm afraid, but I do not have any experiences with iOS. :/

discoveryjames commented 7 years ago

I resolved this issue. I have attached the code for you.

$format = new FFMpeg\Format\Video\X264();
if ($format instanceof VideoInterface) {
    if (null !== $format->getVideoCodec()) {
        $filters->add(new SimpleFilter(['-vprofile', 'baseline']));
        $filters->add(new SimpleFilter(['-level', 3.0]));
    }
}
LaurentMarquet commented 7 years ago

@discoveryjames thanks for your answer but what is the value of $filters?

discoveryjames commented 7 years ago

I would also recommend using the faststart flag too, so that the whole file doesn't have to download before it starts playing.

$format = new FFMpeg\Format\Video\X264();
if ($format instanceof VideoInterface) {
    if (null !== $format->getVideoCodec()) {
         $filters->add(new SimpleFilter(['-vprofile', 'baseline']));
        $filters->add(new SimpleFilter(['-level', 3.0]));
        $filters->add(new SimpleFilter(['-movflags', '+faststart']));
    }
}
LaurentMarquet commented 7 years ago

Thanks for that adding (didn't know about faststart) but what about $filters value (see my question above)?

discoveryjames commented 7 years ago

Sorry for the confusion. I have rewritten the code anyway, I'm using the line below. This is using a php7 array, but if you're not using php7 you can just use array() instead of []

$format = new FFMpeg\Format\Video\X264();
$format->setAdditionalParameters(['-vprofile', 'baseline', '-level', 3.0, '-movflags', '+faststart']);
LaurentMarquet commented 7 years ago

So, I have tried using your code but I have an encoding failed error

'/usr/bin/avconv' '-y' '-i' '/.../video-master.mov' '-metadata:s:v:0' 'rotate=0' '-async' '1' '-metadata:s:v:0' 'start_time=0' '-metadata' 'title=Title' '-ss' '0  
  0:00:00.00' '-t' '00:01:30.00' '-r' '29.97' '-b_strategy' '1' '-bf' '3' '-g' '48' '-threads' '4' '-vcodec' 'libx264' '-acodec' 'libfaac' '-b:v' '1000k' '-refs' '6' '-coder' '1' '-sc_threshold' '40' '-flags' '+loop' '-me_range' '16' '-subq' '7' '-i_qfactor' '0.71' '-  
  qcomp' '0.6' '-qdiff' '4' '-trellis' '1' '-b:a' '128k' '-vprofile' 'baseline' '-level' '3' '-movflags' '+faststart' '-vf' 'movie=/.../edlo.eu/src/AppBundle/Command/../../../web/images/stamp.png [watermark];[in]transpose=1[p0];[p0]scale=854:480 [p1];[p1]  
  [watermark] overlay=main_w - 50 - overlay_w:main_h - 50 - overlay_h [out]' '-pass' '1' '-passlogfile' '/tmp/ffmpeg-passes598c7977ac09554hoy/pass-598c7977ac17a' '/.../video.mp4'

So, I have also tried with

$format = new X264();
$format
    ->setPasses(1)
    ->setAudioCodec('aac')
    ->setKiloBitrate(1200)
    ->setAudioChannels(2)
    ->setAudioKiloBitrate(126)
    ->setAdditionalParameters(['-vprofile', 'baseline', '-level', 3.0, '-movflags', '+faststart'])
;

This one works and is currently in https://edlo.eu/6gkfwk/img/video.mp4, but it doesn't display on my iPhone 6... But well displayed on Firefox.

remyzv commented 5 years ago

Hi @LaurentMarquet

Did you find a way to encode your videos for iOS support ? I absolutly need it.

Thanks !

LaurentMarquet commented 5 years ago

No I didn't find a way...

j-dawg commented 5 years ago

I got it working using the following

$ffmpeg = FFMpeg\FFMpeg::create([
            'ffmpeg.binaries'  => '/usr/bin/ffmpeg',
            'ffprobe.binaries' => '/usr/bin/ffprobe',
            'timeout'          => 3600,
            'ffmpeg.threads'   => 12,
        ]);

        $format = new FFMpeg\Format\Video\X264();
        $format->setAdditionalParameters(['-vprofile', 'baseline', '-level', 3.0, '-movflags', '+faststart']);
LaurentMarquet commented 5 years ago

Thanks for your answer! I have tried (after a long-time not working on the project) and I can display it on iPhone! The settings are the same as the ones I've tried with in August 2017, but now it works. But maybe it was already the case in 2017, because (today) I saw that when I access the file directly, I can see it, but when I use a Symfony controller that return the file content + mime type it doesn't show, so maybe the problem was from here... I don't know. Thanks to all for your time, trials, etc. ! I can now close this issue :)

LaurentMarquet commented 5 years ago

So it's confirmed... The problem was not only with the settings of the saved video but also with the Response sent by Symfony (in fact even with PHP directly it doesn't work). Using Response + file_get_contents() doesn't work on iDevice, but using Symfony\Component\HttpFoundation\BinaryFileResponse works and displays the video correctly.

We still have to use special settings to format videos for iDevices.

jens1o commented 4 years ago

updated the title for more clarity

atastycookie commented 3 years ago

still actual :)